after google launch their new FCM SDK with iOS capability i was abel to receive the notification from the console well either the app was in background or in active state now i am trying to make my server send the push notification as mentioned here i follow up the documentation steps as mention in https://firebase.google.com/docs/cloud-messaging/server but the result is :
{"multicast_id":XXXXXXXXXXXXXXX,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}
The PHP Script
<?php
define( 'API_ACCESS_KEY', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
$registrationIds = array('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
$msg = array
(
'message' => 'here is a message. message',
'title' => 'This is a title. title',
'subtitle' => 'This is a subtitle. subtitle',
'tickerText' => 'Ticker text here...Ticker text here...Ticker text here',
'vibrate' => 1,
'sound' => 1,
'largeIcon' => 'large_icon',
'smallIcon' => 'small_icon'
);
$fields = array
(
'registration_ids' => $registrationIds,
'data' => $msg
);
$headers = array
(
'Authorization: key=' . API_ACCESS_KEY,
'Content-Type: application/json',
);
$ch = curl_init();
curl_setopt( $ch,CURLOPT_URL, 'https://fcm.googleapis.com/fcm/send' );
curl_setopt( $ch,CURLOPT_POST, true );
curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $ch,CURLOPT_POSTFIELDS, json_encode( $fields ) );
$result = curl_exec($ch );
curl_close( $ch );
echo $result;
?>
this code was found on github https://gist.github.com/prime31/5675017 but i make some modification to fit my case
and after searching for related question on stack i just found the following Notification in iOS using new Firebase Messaging SDK, and Invalid Registration Id with my gcm php server but they could not help me in solving my problem.
here is the cliend side but as i mentioned before the app receive the notification from the console.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
float ver = [[[UIDevice currentDevice] systemVersion] floatValue];
if(ver >= 8 && ver<9)
{
if ([[UIApplication sharedApplication] respondsToSelector:#selector(registerUserNotificationSettings:)])
{
[[UIApplication sharedApplication] registerForRemoteNotifications];
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert) categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
}
}else if (ver >=9){
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
else{
//iOS6 and iOS7 specific code
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeAlert];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(tokenRefreshNotification:)
name:kFIRInstanceIDTokenRefreshNotification object:nil];
[FIRApp configure];
[self connectToFcm];
// Override point for customization after application launch.
return YES;
}
- (void)tokenRefreshNotification:(NSNotification *)notification {
// Note that this callback will be fired everytime a new token is generated, including the first
// time. So if you need to retrieve the token as soon as it is available this is where that
// should be done.
NSString *refreshedToken = [[FIRInstanceID instanceID] token];
NSLog(#"InstanceID token: %#", refreshedToken);
}
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSDictionary *newUserInfo = [userInfo objectForKey:#"notification"];
NSLog(#"the user info is : %#",newUserInfo);
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (state == UIApplicationStateBackground || state == UIApplicationStateInactive)
{
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.userInfo = newUserInfo;
localNotification.alertTitle = [newUserInfo objectForKey:#"title"];
localNotification.alertBody = [newUserInfo objectForKey:#"body"];
localNotification.applicationIconBadgeNumber = 0;
localNotification.soundName = UILocalNotificationDefaultSoundName;
localNotification.fireDate = [NSDate date];
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
}
else if (state == UIApplicationStateActive) {
AudioServicesPlaySystemSound(1054);
UIImage *icon = [UIImage imageNamed:#"placeholderimage"];
NSString *title = [newUserInfo objectForKey:#"title"];
NSString *body = [newUserInfo objectForKey:#"body"];
notification = [MPGNotification notificationWithTitle:title subtitle:body backgroundColor:[UIColor colorWithRed:51/255.0 green:51/255.0 blue:51/255.0 alpha:1.0f] iconImage:icon];
notification.duration = 5.0;
notification.swipeToDismissEnabled = NO;
[notification show];
}
}
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:#"<>"]];
token = [token stringByReplacingOccurrencesOfString:#" " withString:#""];
NSLog(#"content---%#", token);
}
- (void)connectToFcm {
[[FIRMessaging messaging] connectWithCompletion:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(#"Unable to connect to FCM. %#", error);
} else {
NSLog(#"Connected to FCM.");
}
}];
}
Related
I am working with FCM notification for our android application. Here i am having problem with FCM notification response. Which is given below code and response.
<?php
// API access key from Google API's Console
define( 'API_ACCESS_KEY', 'xxxxxxxxxxxxxxxxx' );
$registrationIds = array('id'=>'xxxxxxxxxxxxxxxx');
// prep the bundle
$msg = array
(
'message' => 'here is a message. message',
'title' => 'This is a title. title',
);
$fields = array
(
'registration_ids' => $registrationIds,
'data' => $msg
);
$headers = array
(
'Authorization: key=' . API_ACCESS_KEY,
'Content-Type: application/json'
);
$ch = curl_init();
curl_setopt( $ch,CURLOPT_URL, 'https://android.googleapis.com/gcm/send' );
curl_setopt( $ch,CURLOPT_POST, true );
curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $ch,CURLOPT_POSTFIELDS, json_encode( $fields ) );
$result = curl_exec($ch );
curl_close( $ch );
echo $result;
Response:
{message = here is a message. message, title = This is a title. title }
But above response is not proper json format. Kindly please help me that how to send the FCM notification response as a proper JSON format.
Android code is given below:
public class FireMsgService extends FirebaseMessagingService {
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
Log.e("remoteMessage-", "remoteMessage--->" + remoteMessage.getData());
// Log.e("getnotification-", "getnotification--->" + remoteMessage.getNotification());
Intent intent = new Intent(this, EventFragment.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 1410, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri sound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
//.setSmallIcon(getNotificationIcon())
.setContentTitle("Event Application")
.setContentText("Quiz notification")
.setAutoCancel(true)
.setSound(sound)
.setContentIntent(pendingIntent)
.setSmallIcon(getNotificationIcon());
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
private int getNotificationIcon() {
boolean useWhiteIcon = (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP);
return useWhiteIcon ? R.drawable.a_option : R.drawable.a_option;
}
}
i am getting response using remoteMessage.getData().
and Firebase ID class is
public class FireIDService extends FirebaseInstanceIdService {
#Override
public void onTokenRefresh() {
super.onTokenRefresh();
String tkn = FirebaseInstanceId.getInstance().getToken();
Log.e("tkn","tkn---->"+tkn);
}
}
Firebase send the Push notification message as a Map object. To get the map as JSON formate you can use the JSONObject class.
So, The parsing will be like this->
JSONObject jsonData = new JSONObject(remoteMessage.getData());
You can also make a POJO class and parse using GSON library.
Extremely grateful for any help with this.. all I want to do is use my php code to send notifications to all users subscribed to topic "global". Does anyone know why it might not work? Since I want everyone using the app to get the notifications, I will subscribe everyone (unless there is a better way). Here is my php to try to send the notification to my topic global:
<?php
define( 'API_ACCESS_KEY', 'hidden...hidden' );
$msg = array
(
'message' => 'here is a message. message',
'title' => 'This is a title. title',
'vibrate' => 1,
'sound' => 1
);
$fields = array
(
'to' => "/topics/global",
'data' => $msg,
'priority' => 'high'
);
$headers = array
(
'Authorization: key=' . API_ACCESS_KEY,
'Content-Type: application/json'
);
$ch = curl_init();
curl_setopt( $ch,CURLOPT_URL, 'https://android.googleapis.com/gcm/send' );
curl_setopt( $ch,CURLOPT_POST, true );
curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $ch,CURLOPT_POSTFIELDS, json_encode( $fields ) );
$result = curl_exec( $ch );
curl_close( $ch );
echo $result;
?>
I am lacking knowledge but from the $result echo it didn't look like any failure message. This is what I got:
{"message_id":7591682951632927615}
In my Firebase console, I cannot even see the topic "global" so I can't test that sending to the topic works on my device. From what I read online, it could take awhile for a subscribed topic to appear in the console. Just to clarify, sending notifications to all devices using user segment set to the App works in the console!
Is there anything that I can do to verify that my app is actually subscribing users to the topic "global"? Maybe this is the problem. Here is the relevant swift code:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FIRApp.configure()
if #available(iOS 10.0, *) {
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
// For iOS 10 data message (sent via FCM)
FIRMessaging.messaging().remoteMessageDelegate = self
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
return true
}
func applicationReceivedRemoteMessage(_ remoteMessage: FIRMessagingRemoteMessage) {
print("applicationReceivedRemoteMessage")
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
if let refreshedToken = FIRInstanceID.instanceID().token() {
print("InstanceID token: \(refreshedToken)")
FIRMessaging.messaging().subscribe(toTopic: "/topics/global")
}
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
/*
// Print message ID.
if let messageID = userInfo["gcmMessageIDKey"] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
*/
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
if application.applicationState == UIApplicationState.active {
print("GOT IN HERE")
var pushNotificationMessage = ""
if let aps = userInfo["aps"] as? NSDictionary {
if let alert = aps["alert"] as? NSDictionary {
if let message = alert["message"] as? NSString {
pushNotificationMessage = message as String
}
} else if let alert = aps["alert"] as? NSString {
pushNotificationMessage = alert as String
}
}
let notificationAlert = UIAlertController(title: nil, message: pushNotificationMessage, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: {
(alert: UIAlertAction!) -> Void in
})
defaultAction.setValue(Constants.activePushNotificationOKColor, forKey: "titleTextColor")
notificationAlert.addAction(defaultAction)
self.window?.rootViewController?.present(notificationAlert, animated: true, completion: nil)
}
}
To send a notification, store the parameters in notification, not data:
$fields = array
(
'to' => "/topics/global",
'notification' => $msg, // <= CHANGED
'priority' => 'high'
);
Also look at Table 2a in the documentation for Notification payload support. message is not supported, use body instead.
$msg = array
(
'body' => 'here is a message. message', // <= CHANGED
'title' => 'This is a title. title',
'vibrate' => 1,
'sound' => 1
);
I have configured GCM in my existing app and i am receiving the notifications there. Now i am facing two problem:
1) I am not receiving notifications When i exit the application or application is in the background.
2) I am not receiving notification in iphone's notification area, only when my app is running i only direct receive alert message there. And when i pull down notification area i get this message in xcode's console "Could not connect to GCM: The operation couldn’t be completed. (com.google.gcm error 2001.)"
My PHP file is below
<?php
// Payload data you want to send to iOSdevice(s)
// (it will be accessible via intent extras)
$data = array( 'message' => 'Hello World!');
// The recipient registration tokens for this notification
// http://developer.android.com/google/gcm/
$ids = array( 'kucy6xoUmx********eeRsla' );
// Send a GCM push
sendGoogleCloudMessage( $data, $ids );
function sendGoogleCloudMessage( $data, $ids )
{
// Insert real GCM API key from Google APIs Console
// https://code.google.com/apis/console/
$apiKey = 'AIz******9JA';
// Define URL to GCM endpoint
$url = 'https://gcm-http.googleapis.com/gcm/send';
// Set GCM post variables (device IDs and push payload)
$post = array(
'registration_ids' => $ids,
'data' => $data,
);
// Set CURL request headers (authentication and type)
$headers = array(
'Authorization: key=' . $apiKey,
'Content-Type: application/json'
);
// Initialize curl handle
$ch = curl_init();
// Set URL to GCM endpoint
curl_setopt( $ch, CURLOPT_URL, $url );
// Set request method to POST
curl_setopt( $ch, CURLOPT_POST, true );
// Set our custom headers
curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
// Get the response back as string instead of printing it
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
// Set JSON post data
curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( $post ) );
// Actually send the push
$result = curl_exec( $ch );
// Error handling
if ( curl_errno( $ch ) )
{
echo 'GCM error: ' . curl_error( $ch );
}
// Close curl handle
curl_close( $ch );
// Debug GCM response
echo $result;
}
?>
Here is my AppDelegate.m file
// [START register_for_remote_notifications]
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// [START_EXCLUDE]
_registrationKey = #"onRegistrationCompleted";
_messageKey = #"onMessageReceived";
// Configure the Google context: parses the GoogleService-Info.plist, and initializes
// the services that have entries in the file
NSError* configureError;
[[GGLContext sharedInstance] configureWithError:&configureError];
NSAssert(!configureError, #"Error configuring Google services: %#", configureError);
_gcmSenderID = [[[GGLContext sharedInstance] configuration] gcmSenderID];
// Register for remote notifications
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
// iOS 7.1 or earlier
UIRemoteNotificationType allNotificationTypes =
(UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge);
[application registerForRemoteNotificationTypes:allNotificationTypes];
} else {
// iOS 8 or later
// [END_EXCLUDE]
UIUserNotificationType allNotificationTypes =
(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
UIUserNotificationSettings *settings =
[UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
// [END register_for_remote_notifications]
// [START start_gcm_service]
GCMConfig *gcmConfig = [GCMConfig defaultConfig];
gcmConfig.receiverDelegate = self;
[[GCMService sharedInstance] startWithConfig:gcmConfig];
// [END start_gcm_service]
__weak typeof(self) weakSelf = self;
// Handler for registration token request
_registrationHandler = ^(NSString *registrationToken, NSError *error){
if (registrationToken != nil) {
weakSelf.registrationToken = registrationToken;
NSLog(#"Registration Token: %#", registrationToken);
[weakSelf subscribeToTopic];
NSDictionary *userInfo = #{#"registrationToken":registrationToken};
[[NSNotificationCenter defaultCenter] postNotificationName:weakSelf.registrationKey
object:nil
userInfo:userInfo];
} else {
NSLog(#"Registration to GCM failed with error: %#", error.localizedDescription);
NSDictionary *userInfo = #{#"error":error.localizedDescription};
[[NSNotificationCenter defaultCenter] postNotificationName:weakSelf.registrationKey
object:nil
userInfo:userInfo];
}
};
return YES;
}
- (void)subscribeToTopic {
// If the app has a registration token and is connected to GCM, proceed to subscribe to the
// topic
if (_registrationToken && _connectedToGCM) {
[[GCMPubSub sharedInstance] subscribeWithToken:_registrationToken
topic:SubscriptionTopic
options:nil
handler:^(NSError *error) {
if (error) {
// Treat the "already subscribed" error more gently
if (error.code == 3001) {
NSLog(#"Already subscribed to %#",
SubscriptionTopic);
} else {
NSLog(#"Subscription failed: %#",
error.localizedDescription);
}
} else {
self.subscribedToTopic = true;
NSLog(#"Subscribed to %#", SubscriptionTopic);
}
}];
}
}
// [START connect_gcm_service]
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Connect to the GCM server to receive non-APNS notifications
[[GCMService sharedInstance] connectWithHandler:^(NSError *error) {
if (error) {
NSLog(#"Could not connect to GCM: %#", error.localizedDescription);
} else {
_connectedToGCM = true;
NSLog(#"Connected to GCM");
// [START_EXCLUDE]
[self subscribeToTopic];
// [END_EXCLUDE]
}
}];
}
// [END connect_gcm_service]
// [START disconnect_gcm_service]
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[GCMService sharedInstance] disconnect];
// [START_EXCLUDE]
_connectedToGCM = NO;
// [END_EXCLUDE]
}
// [END disconnect_gcm_service]
// [START receive_apns_token]
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// [END receive_apns_token]
// [START get_gcm_reg_token]
// Create a config and set a delegate that implements the GGLInstaceIDDelegate protocol.
GGLInstanceIDConfig *instanceIDConfig = [GGLInstanceIDConfig defaultConfig];
instanceIDConfig.delegate = self;
// Start the GGLInstanceID shared instance with the that config and request a registration
// token to enable reception of notifications
[[GGLInstanceID sharedInstance] startWithConfig:instanceIDConfig];
_registrationOptions = #{kGGLInstanceIDRegisterAPNSOption:deviceToken,
kGGLInstanceIDAPNSServerTypeSandboxOption:#"NO"};
[[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:_gcmSenderID
scope:kGGLInstanceIDScopeGCM
options:_registrationOptions
handler:_registrationHandler];
// [END get_gcm_reg_token]
}
// [START receive_apns_token_error]
- (void)application:(UIApplication *)application
didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(#"Registration for remote notification failed with error: %#", error.localizedDescription);
// [END receive_apns_token_error]
NSDictionary *userInfo = #{#"error" :error.localizedDescription};
[[NSNotificationCenter defaultCenter] postNotificationName:_registrationKey
object:nil
userInfo:userInfo];
}
// [START ack_message_reception]
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo {
NSLog(#"Notification received: %#", userInfo);
// This works only if the app started the GCM service
[[GCMService sharedInstance] appDidReceiveMessage:userInfo];
// Handle the received message
// [START_EXCLUDE]
[[NSNotificationCenter defaultCenter] postNotificationName:_messageKey
object:nil
userInfo:userInfo];
// [END_EXCLUDE]
}
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))handler {
NSLog(#"Notification received: %#", userInfo);
// This works only if the app started the GCM service
[[GCMService sharedInstance] appDidReceiveMessage:userInfo];
// Handle the received message
// Invoke the completion handler passing the appropriate UIBackgroundFetchResult value
// [START_EXCLUDE]
[[NSNotificationCenter defaultCenter] postNotificationName:_messageKey
object:nil
userInfo:userInfo];
handler(UIBackgroundFetchResultNoData);
// [END_EXCLUDE]
}
// [END ack_message_reception]
// [START on_token_refresh]
- (void)onTokenRefresh {
// A rotation of the registration tokens is happening, so the app needs to request a new token.
NSLog(#"The GCM registration token needs to be changed.");
[[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:_gcmSenderID
scope:kGGLInstanceIDScopeGCM
options:_registrationOptions
handler:_registrationHandler];
}
// [END on_token_refresh]
// [START upstream_callbacks]
- (void)willSendDataMessageWithID:(NSString *)messageID error:(NSError *)error {
if (error) {
// Failed to send the message.
} else {
// Will send message, you can save the messageID to track the message
}
}
- (void)didSendDataMessageWithID:(NSString *)messageID {
// Did successfully send message identified by messageID
}
// [END upstream_callbacks]
- (void)didDeleteMessagesOnServer {
// Some messages sent to this device were deleted on the GCM server before reception, likely
// because the TTL expired. The client should notify the app server of this, so that the app
// server can resend those messages.
}
I am not a php script master so please help me that how i can resolve my issues.
I have added
'content_available' => true,//to trigger when iOS app is in background
'priority' => 'high',
'notification' => $data,
$data = array( 'message' => 'Hello World!', 'body' => 'Hello World!');
to your code.
Please try below code;
<?php
// Payload data you want to send to iOSdevice(s)
// (it will be accessible via intent extras)
$data = array( 'message' => 'Hello World!', 'body' => 'Hello World!');
// The recipient registration tokens for this notification
// http://developer.android.com/google/gcm/
$ids = array( 'kucy6xoUmx********eeRsla' );
// Send a GCM push
sendGoogleCloudMessage( $data, $ids );
function sendGoogleCloudMessage( $data, $ids )
{
// Insert real GCM API key from Google APIs Console
// https://code.google.com/apis/console/
$apiKey = 'AIz******9JA';
// Define URL to GCM endpoint
$url = 'https://gcm-http.googleapis.com/gcm/send';
// Set GCM post variables (device IDs and push payload)
$post = array(
'registration_ids' => $ids,
'data' => $data,
'content_available' => true,
'priority' => 'high',
'notification' => $data,
);
// Set CURL request headers (authentication and type)
$headers = array(
'Authorization: key=' . $apiKey,
'Content-Type: application/json'
);
// Initialize curl handle
$ch = curl_init();
// Set URL to GCM endpoint
curl_setopt( $ch, CURLOPT_URL, $url );
// Set request method to POST
curl_setopt( $ch, CURLOPT_POST, true );
// Set our custom headers
curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
// Get the response back as string instead of printing it
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
// Set JSON post data
curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( $post ) );
// Actually send the push
$result = curl_exec( $ch );
// Error handling
if ( curl_errno( $ch ) )
{
echo 'GCM error: ' . curl_error( $ch );
}
// Close curl handle
curl_close( $ch );
// Debug GCM response
echo $result;
}
?>
on IOS side;
Follow orders on GCM Site
EDIT 1:
You may try sending notification for ios;
I edited your php code above;
Changes are;
'notification' => $data,
and
$data = array( 'message' => 'Hello World!', 'body' => 'Hello World!');
GCM receiving notification in invalid state 2.
Developers who are using GCM integration for both Android and iOS need to add specific things in their Post call to the GCM Server.
For iOS:
We need two additional things to make the same call work in iOS:
'notification' => $data,
'content_available'=> 'true',
The Apple push notifications service requires content_available is true. And then 'notification'=> $data helps the Notification center to know that the notification has arrived and needs to be pushed to the device.
I'm creating an iOS application using AFMultipartFormData AFNetworking to upload an image onto Wordpress site. On the Wordpress server side, the following data was received when I echoed $_FILES:
media = {
error = (
0
);
name = (
"IMG_0004.JPG"
);
"tmp_name" = (
"C:\\Windows\\Temp\\phpF010.tmp"
);
type = (
"image/jpeg"
);
};
Somehow, Wordpress doesn't recognize my file as a valid image file in wp_check_filetype_and_ext() as I'm getting the following error back:
Error Domain=com.alamofire.error.serialization.response Code=-1011 "Request failed: unsupported media type (415)"
Here is my Wordpress function to handle the file uploaded and insert it into the media directory:
function ldp_image_upload( $request ) {
if ( empty($_FILES) ) {
return new WP_Error( 'Bad Request', 'Missing media file', array( 'status' => 400 ) );
}
$overrides = array('test_form' => false);
$uploaded_file = $_FILES['media'];
$wp_filetype = wp_check_filetype_and_ext( $uploaded_file['tmp_name'], $uploaded_file['name'] );
if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) )
return new WP_Error( 'Unsupported Media Type', 'Invalid image file', array( 'status' => 415 ) );
$file = wp_handle_upload($uploaded_file, $overrides);
if ( isset($file['error']) )
return new WP_Error( 'Internal Server Error', 'Image upload error', array( 'status' => 500 ) );
$url = $file['url'];
$type = $file['type'];
$file = $file['file'];
$filename = basename($file);
// Construct the object array
$object = array(
'post_title' => $filename,
'post_content' => $url,
'post_mime_type' => $type,
'guid' => $url,
);
// Save the data
$id = wp_insert_attachment($object, $file);
if ( !is_wp_error($id) ) {
// Add the meta-data such as thumbnail
wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
}
// Create the response object
$response = new WP_REST_Response( array('result' => 'OK'));
return $response;
}
Here is the code on the front-end to send the image:
- (void)createMedia:(RemoteMedia *)media
forBlogID:(NSNumber *)blogID
progress:(NSProgress **)progress
success:(void (^)(RemoteMedia *remoteMedia))success
failure:(void (^)(NSError *error))failure
{
NSProgress *localProgress = [NSProgress progressWithTotalUnitCount:2];
NSString *path = media.localURL;
NSString *type = media.mimeType;
NSString *filename = media.file;
NSString *apiPath = [NSString stringWithFormat:#"sites/%#/media/new", blogID];
NSString *requestUrl = [self pathForEndpoint:apiPath
withVersion:ServiceRemoteRESTApibbPressExtVersion_1_0];
NSMutableURLRequest *request = [self.api.requestSerializer multipartFormRequestWithMethod:#"POST"
URLString:[[NSURL URLWithString:requestUrl relativeToURL:self.api.baseURL] absoluteString]
parameters:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
NSURL *url = [[NSURL alloc] initFileURLWithPath:path];
[formData appendPartWithFileURL:url name:#"media[]" fileName:filename mimeType:type error:nil];
} error:nil];
AFHTTPRequestOperation *operation = [self.api HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation * operation, id responseObject) {
NSDictionary *response = (NSDictionary *)responseObject;
NSArray * errorList = response[#"error"];
NSArray * mediaList = response[#"media"];
if (mediaList.count > 0){
RemoteMedia * remoteMedia = [self remoteMediaFromJSONDictionary:mediaList[0]];
if (success) {
success(remoteMedia);
}
localProgress.completedUnitCount=localProgress.totalUnitCount;
} else {
DDLogDebug(#"Error uploading file: %#", errorList);
localProgress.totalUnitCount=0;
localProgress.completedUnitCount=0;
NSError * error = nil;
if (errorList.count > 0){
NSDictionary * errorDictionary = #{NSLocalizedDescriptionKey: errorList[0]};
error = [NSError errorWithDomain:WordPressRestApiErrorDomain code:WPRestErrorCodeMediaNew userInfo:errorDictionary];
}
if (failure) {
failure(error);
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
DDLogDebug(#"Error uploading file: %#", [error localizedDescription]);
localProgress.totalUnitCount=0;
localProgress.completedUnitCount=0;
if (failure) {
failure(error);
}
}];
// Setup progress object
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
localProgress.completedUnitCount +=bytesWritten;
}];
unsigned long long size = [[request valueForHTTPHeaderField:#"Content-Length"] longLongValue];
// Adding some extra time because after the upload is done the backend takes some time to process the data sent
localProgress.totalUnitCount = size+1;
localProgress.cancellable = YES;
localProgress.pausable = NO;
localProgress.cancellationHandler = ^(){
[operation cancel];
};
if (progress) {
*progress = localProgress;
}
[self.api.operationQueue addOperation:operation];
}
As far as the mimes type is concern, image/jpeg should be supported by wordpress. Unless C:\\Windows\\Temp\\phpF010.tmp is not a true image, then AFNetworking is sending a corrupted file?
Can anyone offer advice on this? Thanks in advance.
Does $wp_filetype['proper_filename'] return something? I haven't tried this but I think this should return the filename as it should be (i.e. with an extension). If it does, you should move the uploaded file to another location and then rename it with the new filename, the upload should then succeed.
I have created a class which handles the purchases on In-App Purchases and also the validating of receipts. A while ago I used to use the transactionReceipt property on an SKPaymentTransaction, but have updated my code a fair amount and now use appStoreReceiptURL on the [NSBundle mainBundle].
Basically it seems as though my receipt is being sent to Apple's server in an acceptable manner, but I keep getting the status code of 21002. In auto-renewable subscriptions I know that this means the receipt is not in an acceptable format, however I have no idea what this status means in regard to an in-app purchase receipt.
Here is the local method validating the receipt:
/**
* Validates the receipt.
*
* #param transaction The transaction triggering the validation of the receipt.
*/
- (void)validateReceiptForTransaction:(SKPaymentTransaction *)transaction
{
// get the product for the transaction
IAPProduct *product = self.internalProducts[transaction.payment.productIdentifier];
// get the receipt as a base64 encoded string
NSData *receiptData = [[NSData alloc] initWithContentsOfURL:[NSBundle mainBundle].appStoreReceiptURL];
NSString *receipt = [receiptData base64EncodedStringWithOptions:kNilOptions];
NSLog(#"Receipt: %#", receipt);
// determine the url for the receipt verification server
NSURL *verificationURL = [[NSURL alloc] initWithString:IAPHelperServerBaseURL];
verificationURL = [verificationURL URLByAppendingPathComponent:IAPHelperServerReceiptVerificationComponent];
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:verificationURL];
urlRequest.HTTPMethod = #"POST";
NSDictionary *httpBody = #{#"receipt" : receipt,
#"sandbox" : #(1)};
urlRequest.HTTPBody = [NSKeyedArchiver archivedDataWithRootObject:httpBody];
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
// create a block to be called whenever a filue is hit
void (^failureBlock)(NSString *failureMessage) = ^void(NSString *failureMessage)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:
^{
// log the failure message
NSLog(#"%#", failureMessage);
// if we have aready tried refreshing the receipt then we close the transaction to avoid loops
if (self.transactionToValidate)
product.purchaseInProgress = NO,
[[SKPaymentQueue defaultQueue] finishTransaction:transaction],
[self notifyStatus:#"Validation failed." forProduct:product],
self.transactionToValidate = nil;
// if we haven't tried yet, we'll refresh the receipt and then attempt a second validation
else
self.transactionToValidate = transaction,
[self refreshReceipt];
}];
};
// check for an error whilst contacting the server
if (connectionError)
{
failureBlock([[NSString alloc] initWithFormat:#"Failure connecting to server: %#", connectionError]);
return;
}
// cast the response appropriately
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
// parse the JSON
NSError *jsonError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
// if the data did not parse correctly we fail out
if (!json)
{
NSString *responseString = [NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode];
NSString *failureMessage = [[NSString alloc] initWithFormat:#"Failure parsing JSON: %#\nServer Response: %# (%#)",
data, responseString, #(httpResponse.statusCode)];
failureBlock(failureMessage);
return;
}
// if the JSON was successfully parsed pull out status code to check for verification success
NSInteger statusCode = [json[#"status"] integerValue];
NSString *errorDescription = json[#"error"];
// if the verification did not succeed we fail out
if (statusCode != 0)
{
NSString *failureMessage = [[NSString alloc] initWithFormat:#"Failure verifying receipt: %#", errorDescription];
failureBlock(failureMessage);
}
// otherwise we have succeded, yay
else
NSLog(#"Successfully verified receipt."),
[self provideContentForCompletedTransaction:transaction productIdentifier:transaction.payment.productIdentifier];
}];
}
The important PHP function on the server does this:
/**
* Validates a given receipt and returns the result.
*
* #param receipt Base64-encoded receipt.
* #param sandbox Boolean indicating whether to use sandbox servers or production servers.
*
* #return Whether the reciept is valid or not.
*/
function validateReceipt($receipt, $sandbox)
{
// determine url for store based on if this is production or development
if ($sandbox)
$store = 'https://sandbox.itunes.apple.com/verifyReceipt';
else
$store = 'https://buy.itunes.apple.com/verifyReceipt';
// set up json-encoded dictionary with receipt data for apple receipt validator
$postData = json_encode(array('receipt-data' => $receipt));
// use curl library to perform web request
$curlHandle = curl_init($store);
// we want results returned as string, the request to be a post, and the json data to be in the post fields
curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlHandle, CURLOPT_POST, true);
curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $postData);
$encodedResponse = curl_exec($curlHandle);
curl_close($curlHandle);
// if we received no response we return the error
if (!$encodedResponse)
return result(ERROR_VERIFICATION_NO_RESPONSE, 'Payment could not be verified - no response data. This was sandbox? ' . ($sandbox ? 'YES' : 'NO'));
// decode json response and get the data
$response = json_decode($encodedResponse);
$status = $response->{'status'};
$decodedReceipt = $response->{'receipt'};
// if status code is not 0 there was an error validation receipt
if ($status)
return result(ERROR_VERIFICATION_FAILED, 'Payment could not be verified (status = ' . $status . ').');
// log the returned receipt from validator
logToFile(print_r($decodedReceipt, true));
// pull out product id, transaction id and original transaction id from infro trurned by apple
$productID = $decodedReceipt->{'product_id'};
$transactionID = $decodedReceipt->{'transaction_id'};
$originalTransactionID = $decodedReceipt->{'original_transaction_id'};
// make sure product id has expected prefix or we bail
if (!beginsWith($productID, PRODUCT_ID_PREFIX))
return result(ERROR_INVALID_PRODUCT_ID, 'Invalid Product Identifier');
// get any existing record of this transaction id from our database
$db = Database::get();
$statement = $db->prepare('SELECT * FROM transactions WHERE transaction_id = ?');
$statement->bindParam(1, $transactionID, PDO::PARAM_STR, 32);
$statement->execute();
// if we have handled this transaction before return a failure
if ($statement->rowCount())
{
logToFile("Already processed $transactionID.");
return result(ERROR_TRANSACTION_ALREADY_PROCESSED, 'Already processed this transaction.');
}
// otherwise we insert this new transaction into the database
else
{
logToFile("Adding $transactionID.");
$statement = $db->prepare('INSERT INTO transactions(transaction_id, product_id, original_transaction_id) VALUES (?, ?, ?)');
$statement->bindParam(1, $transactionID, PDO::PARAM_STR, 32);
$statement->bindParam(2, $productID, PDO::PARAM_STR, 32);
$statement->bindParam(3, $originalTransactionID, PDO::PARAM_STR, 32);
$statement->execute();
}
return result(SUCCESS);
}
The actual PHP script being executed is:
$receipt = $_POST['receipt'];
$sandbox = $_POST['sandbox'];
$returnValue = validateReceipt($receipt, $sandbox);
header('content-type: application/json; charset=utf-8');
echo json_encode($returnValue);
Comparing your PHP with mine (which I know works) is difficult because I am using HTTPRequest rather than the raw curl APIs. However, it seems to me that you are setting the "{receipt-data:..}" JSON string as merely a field in the POST data rather than as the raw POST data itself, which is what my code is doing.
curl_setopt($curlHandle, CURLOPT_POST, true);
curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $postData); // Possible problem
$encodedResponse = curl_exec($curlHandle);
Compared to:
$postData = '{"receipt-data" : "'.$receipt.'"}'; // yay one-off JSON serialization!
$request = new HTTPRequest('https://sandbox.itunes.apple.com/verifyReceipt', HTTP_METH_POST);
$request->setBody($postData); // Relevant difference...
$request->send();
$encodedResponse = $request->getResponseBody();
I have changed my variable names a bit to make them match up with your example.
the code 21002 means "The data in the receipt-data property was malformed or missing."
you can find it in
https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html
below code is my class for appstore in-app verifyRecepip, GuzzleHttp is required, you can install it by composer require guzzlehttp/guzzle https://github.com/guzzle/guzzle
<?php
namespace App\Libraries;
class AppStoreIAP
{
const SANDBOX_URL = 'https://sandbox.itunes.apple.com/verifyReceipt';
const PRODUCTION_URL = 'https://buy.itunes.apple.com/verifyReceipt';
protected $receipt = null;
protected $receiptData = null;
protected $endpoint = 'production';
public function __construct($receipt, $endpoint = self::PRODUCTION_URL)
{
$this->receipt = json_encode(['receipt-data' => $receipt]);
$this->endpoint = $endpoint;
}
public function setEndPoint($endpoint)
{
$this->endpoint = $endpoint;
}
public function getReceipt()
{
return $this->receipt;
}
public function getReceiptData()
{
return $this->receiptData;
}
public function getEndpoint()
{
return $this->endpoint;
}
public function validate($bundle_id, $transaction_id, $product_code)
{
$http = new \GuzzleHttp\Client([
'headers' => [
'Content-Type' => 'application/x-www-form-urlencoded',
],
'timeout' => 4.0,
]);
$res = $http->request('POST', $this->endpoint, ['body' => $this->receipt]);
$receiptData = json_decode((string) $res->getBody(), true);
$this->receiptData = $receiptData;
switch ($receiptData['status']) {
case 0: // verify Ok
// check bundle_id
if (!empty($receiptData['receipt']['bundle_id'])) {
$receipt_bundle_id = $receiptData['receipt']['bundle_id'];
if ($receipt_bundle_id != $bundle_id) {
throw new \Exception('bundle_id not matched!');
}
}
// check transaction_id , product_id
if (!empty($receiptData['receipt']['in_app'])) {
$in_app = array_combine(array_column($receiptData['receipt']['in_app'], 'transaction_id'), $receiptData['receipt']['in_app']);
if (empty($in_app[$transaction_id])) {
throw new \Exception('transaction_id is empty!');
}
$data = $in_app[$transaction_id];
if ($data['product_id'] != $product_code) {
throw new \Exception('product_id not matched!');
}
} else {
$receipt_transaction_id = $receiptData['receipt']['transaction_id'];
$receipt_product_id = $receiptData['receipt']['product_id'];
if ($receipt_transaction_id != $transaction_id || $product_id != $product_code) {
throw new \Exception('tranaction_id not matched!');
}
}
break;
case 21007:// sandbox order validate in production will return 21007
if ($this->getEndpoint() != self::SANDBOX_URL) {
$this->setEndPoint(self::SANDBOX_URL);
$this->validate($bundle_id, $transaction_id, $product_code);
} else {
throw new \Exception('appstore error!');
}
break;
default:
throw new \Exception("[{$receiptData['status']}]appstore error!");
break;
}
return $receiptData;
}
}
I think Morteza M is correct. I did a test and got reply(JSON) like:
{
'status':
'environment': 'Sandbox'
'receipt':
{
'download_id':
....
'in_app":
{
'product_id':
....
}
....
}
}