Apple Wallet/Passbook 通知无法送达
Apple Wallet/Passbook notifications would not deliver
我正在尝试使用 Pushsharp 发送通知以更新 wallet/passbook 中的数字卡。我仔细检查了所有这些:
- 使用相同的证书签署通行证并更新它们
- 在 Pushsharp 中禁用 production/sandbox 证书检查
- 使用我从设备获得的 pushtoken。它的格式是:d30720c34af46d65e02db3c3db6Ohae04d183dfaa105133f7c21b8d1963629fe
- 使用本教程生成的 .p12 证书,除了步骤 # 10:https://support.airship.com/hc/en-us/articles/213493683-How-to-make-an-Apple-Pass-Type-Certificate
- telnet feedback.push.apple.com 2196 成功
- pass.json 中的 PassTypeIdentifier 与 .p12 文件的通用名称相同
- 卡片在设备上完美打开
- 设备向服务器发送注册请求
- 下拉更新完美运行
但是当我向 APNs 发送通知请求时,设备没有回击。
注意:出于保密原因,我已经更改或删除了以下代码中的网址、标记和路径
下面是我用来发送通知的代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using PushSharp.Apple;
namespace NotificationPushsharp01
{
class Program
{
static void Main(string[] args)
{
string certificatePath = @"PATH_OF_CERTIFICATE.p12";
X509Certificate2 clientCertificate = new X509Certificate2(System.IO.File.ReadAllBytes(certificatePath), "12345");
// Configuration (NOTE: .pfx can also be used here)
var config = new ApnsConfiguration(ApnsConfiguration.ApnsServerEnvironment.Production, clientCertificate, validateIsApnsCertificate: false);
// Create a new broker
var apnsBroker = new ApnsServiceBroker(config);
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
// Wire up events
apnsBroker.OnNotificationFailed += (notification, aggregateEx) =>
{
aggregateEx.Handle(ex =>
{
// See what kind of exception it was to further diagnose
if (ex is ApnsNotificationException notificationException)
{
// Deal with the failed notification
var apnsNotification = notificationException.Notification;
var statusCode = notificationException.ErrorStatusCode;
Console.WriteLine($"Apple Notification Failed: ID={apnsNotification.Identifier}, Code={statusCode}");
}
else
{
// Inner exception might hold more useful information like an ApnsConnectionException
Console.WriteLine($"Apple Notification Failed for some unknown reason : {ex.InnerException}");
}
// Mark it as handled
return true;
});
};
apnsBroker.OnNotificationSucceeded += (notification) =>
{
Console.WriteLine("Apple Notification Sent!");
};
// Start the broker
apnsBroker.Start();
apnsBroker.QueueNotification(new ApnsNotification
{
DeviceToken = "d30710c34af48d65e02db4c3db60fae04d283efaa105633f7c41b8d1963628fe",
Payload = JObject.Parse("{\"aps\":\"\"}")
});
// Stop the broker, wait for it to finish
// This isn't done after every message, but after you're
// done with the broker
apnsBroker.Stop();
Console.ReadLine();
}
}
}
以上代码运行的输出
2019-12-19 08:39:24.AM [DEBUG] Scaled Changed to: 1
2019-12-19 08:39:24.AM [INFO] Stopping: Waiting on Tasks
2019-12-19 08:39:24.AM [INFO] Waiting on all tasks 1
2019-12-19 08:39:25.AM [INFO] APNS-Client[1]: Sending Batch ID=1, Count=1
2019-12-19 08:39:25.AM [INFO] APNS-Client[1]: Connecting (Batch ID=1)
2019-12-19 08:39:25.AM [INFO] APNS-Client[1]: Connected (Batch ID=1)
2019-12-19 08:39:25.AM [INFO] APNS-Client[1]: Sent Batch, waiting for possible response...
2019-12-19 08:39:26.AM [INFO] APNS-Client[1]: Received -1 bytes response...
2019-12-19 08:39:26.AM [INFO] APNS-Client[1]: Batch (ID=1) completed with no error response...
2019-12-19 08:39:26.AM [INFO] APNS-Client[1]: Done Reading for Batch ID=1, reseting batch timer...
Apple Notification Sent!
2019-12-19 08:39:26.AM [INFO] All Tasks Finished
2019-12-19 08:39:26.AM [INFO] Passed WhenAll
2019-12-19 08:39:26.AM [INFO] Broker IsCompleted
2019-12-19 08:39:26.AM [DEBUG] Broker Task Ended
2019-12-19 08:39:26.AM [INFO] Stopping: Done Waiting on Tasks
API 的控制器,负责处理通行证的注册和更新
//Sorry for the comments
public class DevicesController : ApiController
{
// GET request to webServiceURL/version/devices/deviceLibraryIdentifier/registrations/passTypeIdentifier
[HttpGet]
public HttpResponseMessage GetSerialNumber(string deviceLibraryIdentifier, string passTypeIdentifier)
{
System.IO.File.AppendAllText(@"updates.txt", "Device requested serial numbers S");
System.IO.File.AppendAllText(@"updates.txt", System.Environment.NewLine);
// For example...
SerialNumbers lastUpdateToSerialNumDict = new SerialNumbers();
// LastUpdated timestamp set to current datetime
lastUpdateToSerialNumDict.lastUpdated = String.Format("{0:MM/dd/yyyy HH:mm:ss}", DateTime.Now);
// A list of serial numbers got from database
lastUpdateToSerialNumDict.serialNumbers = new List<string>();
lastUpdateToSerialNumDict.serialNumbers.Add("123456789");
string jsonRes = JsonConvert.SerializeObject(lastUpdateToSerialNumDict);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(jsonRes, Encoding.UTF8, "application/json");
return response;
}
// GET request to webServiceURL/version/devices/deviceLibraryIdentifier/registrations/passTypeIdentifier?passesUpdatedSince=tag
[HttpGet]
public HttpResponseMessage GetSerialNumber(string deviceLibraryIdentifier, string passTypeIdentifier, string passesUpdatedSince)
{
System.IO.File.AppendAllText(@"updates.txt", "Device requested serial numbers C");
System.IO.File.AppendAllText(@"updates.txt", System.Environment.NewLine);
// For example...
SerialNumbers lastUpdateToSerialNumDict = new SerialNumbers();
// LastUpdated timestamp set to current datetime
lastUpdateToSerialNumDict.lastUpdated = String.Format("{0:MM/dd/yyyy HH:mm:ss}", DateTime.Now);
// A list of serial numbers got from database
lastUpdateToSerialNumDict.serialNumbers = new List<string>();
lastUpdateToSerialNumDict.serialNumbers.Add("123456789");
string jsonRes = JsonConvert.SerializeObject(lastUpdateToSerialNumDict);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(jsonRes, Encoding.UTF8, "application/json");
return response;
}
// POST request to webServiceURL/version/devices/deviceLibraryIdentifier/registrations/passTypeIdentifier/serialNumber
[HttpPost]
public HttpResponseMessage RegisterDevice(string deviceLibraryIdentifier, string passTypeIdentifier, string serialNumber, [FromBody]HR.CardApi.Api.Models.DevicesPayload payload)
{
//ApiLog
System.IO.File.AppendAllText(@"ApiLog.txt", "devices/register was used");
System.IO.File.AppendAllText(@"D:\FGC-Microservice\HR.CardApi.DDC_31072019\HR.CardApi.Api\bin\ApiLog.txt", System.Environment.NewLine);
//ApiLog
System.IO.File.AppendAllText(@"data.txt", deviceLibraryIdentifier);
System.IO.File.AppendAllText(@"data.txt", System.Environment.NewLine);
System.IO.File.AppendAllText(@"data.txt", passTypeIdentifier);
System.IO.File.AppendAllText(@"data.txt", System.Environment.NewLine);
System.IO.File.AppendAllText(@"data.txt", serialNumber);
System.IO.File.AppendAllText(@"data.txt", System.Environment.NewLine);
System.IO.File.AppendAllText(@"data.txt", payload.pushToken);
System.IO.File.AppendAllText(@"data.txt", System.Environment.NewLine);
System.IO.File.AppendAllText(@"data.txt", System.Environment.NewLine);
System.Net.Http.HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
// DELETE request to webServiceURL/version/devices/deviceLibraryIdentifier/registrations/passTypeIdentifier/serialNumber
[HttpDelete]
public HttpResponseMessage UnRegisterDevice(string deviceLibraryIdentifier, string passTypeIdentifier, string serialNumber)
{
//Udpate Devices and Register table
System.Net.Http.HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
}
pass.json 个 pass.pkpass 个文件
{
"passTypeIdentifier": "SAME_AS_IN_KEYCHAIN",
"formatVersion": 1,
"serialNumber": "123456789",
"description": "CONFIDENTIAL",
"organizationName": "CONFIDENTIAL",
"teamIdentifier": "CONFIDENTIAL",
"voided": false,
"barcodes": [
{
"format": "PKBarcodeFormatPDF417",
"message": "1234587568464654",
"messageEncoding": "ISO-8859-1"
}
],
"storeCard": {
"headerFields": [],
"primaryFields": [],
"secondaryFields": [],
"auxiliaryFields": [
{
"key": "offer",
"label": "Initial Pass",
"value": "8979787645464654"
}
],
"backFields": [
{
"key": "CONFIDENTIAL",
"label": "CONFIDENTIAL",
"attributedValue": "CONFIDENTIAL",
"value": "CONFIDENTIAL"
}
]
},
"authenticationToken": "wxyzd7J8AlYYFPS0k0a0FfVFtq0ewzEfd",
"webServiceURL": "https://CONFIDENTIAL.com"
}
您可以提出任何与代码相关的问题。目前,我正在检查设备日志。如果我发现有用的东西,我会更新。
感谢您的宝贵时间。
我明白问题出在哪里了。有问题的代码是正确的。它没有错误。
当设备请求序列号时,API 返回 HTTP 404。这是因为我的通行证的 passTypeIdentifier 中包含点。就像 pass.acbcd.abcd。
我刚刚将我的 API 配置为允许 url 中的点,一切都开始正常工作。
我正在尝试使用 Pushsharp 发送通知以更新 wallet/passbook 中的数字卡。我仔细检查了所有这些:
- 使用相同的证书签署通行证并更新它们
- 在 Pushsharp 中禁用 production/sandbox 证书检查
- 使用我从设备获得的 pushtoken。它的格式是:d30720c34af46d65e02db3c3db6Ohae04d183dfaa105133f7c21b8d1963629fe
- 使用本教程生成的 .p12 证书,除了步骤 # 10:https://support.airship.com/hc/en-us/articles/213493683-How-to-make-an-Apple-Pass-Type-Certificate
- telnet feedback.push.apple.com 2196 成功
- pass.json 中的 PassTypeIdentifier 与 .p12 文件的通用名称相同
- 卡片在设备上完美打开
- 设备向服务器发送注册请求
- 下拉更新完美运行
但是当我向 APNs 发送通知请求时,设备没有回击。
注意:出于保密原因,我已经更改或删除了以下代码中的网址、标记和路径
下面是我用来发送通知的代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using PushSharp.Apple;
namespace NotificationPushsharp01
{
class Program
{
static void Main(string[] args)
{
string certificatePath = @"PATH_OF_CERTIFICATE.p12";
X509Certificate2 clientCertificate = new X509Certificate2(System.IO.File.ReadAllBytes(certificatePath), "12345");
// Configuration (NOTE: .pfx can also be used here)
var config = new ApnsConfiguration(ApnsConfiguration.ApnsServerEnvironment.Production, clientCertificate, validateIsApnsCertificate: false);
// Create a new broker
var apnsBroker = new ApnsServiceBroker(config);
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
// Wire up events
apnsBroker.OnNotificationFailed += (notification, aggregateEx) =>
{
aggregateEx.Handle(ex =>
{
// See what kind of exception it was to further diagnose
if (ex is ApnsNotificationException notificationException)
{
// Deal with the failed notification
var apnsNotification = notificationException.Notification;
var statusCode = notificationException.ErrorStatusCode;
Console.WriteLine($"Apple Notification Failed: ID={apnsNotification.Identifier}, Code={statusCode}");
}
else
{
// Inner exception might hold more useful information like an ApnsConnectionException
Console.WriteLine($"Apple Notification Failed for some unknown reason : {ex.InnerException}");
}
// Mark it as handled
return true;
});
};
apnsBroker.OnNotificationSucceeded += (notification) =>
{
Console.WriteLine("Apple Notification Sent!");
};
// Start the broker
apnsBroker.Start();
apnsBroker.QueueNotification(new ApnsNotification
{
DeviceToken = "d30710c34af48d65e02db4c3db60fae04d283efaa105633f7c41b8d1963628fe",
Payload = JObject.Parse("{\"aps\":\"\"}")
});
// Stop the broker, wait for it to finish
// This isn't done after every message, but after you're
// done with the broker
apnsBroker.Stop();
Console.ReadLine();
}
}
}
以上代码运行的输出
2019-12-19 08:39:24.AM [DEBUG] Scaled Changed to: 1
2019-12-19 08:39:24.AM [INFO] Stopping: Waiting on Tasks
2019-12-19 08:39:24.AM [INFO] Waiting on all tasks 1
2019-12-19 08:39:25.AM [INFO] APNS-Client[1]: Sending Batch ID=1, Count=1
2019-12-19 08:39:25.AM [INFO] APNS-Client[1]: Connecting (Batch ID=1)
2019-12-19 08:39:25.AM [INFO] APNS-Client[1]: Connected (Batch ID=1)
2019-12-19 08:39:25.AM [INFO] APNS-Client[1]: Sent Batch, waiting for possible response...
2019-12-19 08:39:26.AM [INFO] APNS-Client[1]: Received -1 bytes response...
2019-12-19 08:39:26.AM [INFO] APNS-Client[1]: Batch (ID=1) completed with no error response...
2019-12-19 08:39:26.AM [INFO] APNS-Client[1]: Done Reading for Batch ID=1, reseting batch timer...
Apple Notification Sent!
2019-12-19 08:39:26.AM [INFO] All Tasks Finished
2019-12-19 08:39:26.AM [INFO] Passed WhenAll
2019-12-19 08:39:26.AM [INFO] Broker IsCompleted
2019-12-19 08:39:26.AM [DEBUG] Broker Task Ended
2019-12-19 08:39:26.AM [INFO] Stopping: Done Waiting on Tasks
API 的控制器,负责处理通行证的注册和更新
//Sorry for the comments
public class DevicesController : ApiController
{
// GET request to webServiceURL/version/devices/deviceLibraryIdentifier/registrations/passTypeIdentifier
[HttpGet]
public HttpResponseMessage GetSerialNumber(string deviceLibraryIdentifier, string passTypeIdentifier)
{
System.IO.File.AppendAllText(@"updates.txt", "Device requested serial numbers S");
System.IO.File.AppendAllText(@"updates.txt", System.Environment.NewLine);
// For example...
SerialNumbers lastUpdateToSerialNumDict = new SerialNumbers();
// LastUpdated timestamp set to current datetime
lastUpdateToSerialNumDict.lastUpdated = String.Format("{0:MM/dd/yyyy HH:mm:ss}", DateTime.Now);
// A list of serial numbers got from database
lastUpdateToSerialNumDict.serialNumbers = new List<string>();
lastUpdateToSerialNumDict.serialNumbers.Add("123456789");
string jsonRes = JsonConvert.SerializeObject(lastUpdateToSerialNumDict);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(jsonRes, Encoding.UTF8, "application/json");
return response;
}
// GET request to webServiceURL/version/devices/deviceLibraryIdentifier/registrations/passTypeIdentifier?passesUpdatedSince=tag
[HttpGet]
public HttpResponseMessage GetSerialNumber(string deviceLibraryIdentifier, string passTypeIdentifier, string passesUpdatedSince)
{
System.IO.File.AppendAllText(@"updates.txt", "Device requested serial numbers C");
System.IO.File.AppendAllText(@"updates.txt", System.Environment.NewLine);
// For example...
SerialNumbers lastUpdateToSerialNumDict = new SerialNumbers();
// LastUpdated timestamp set to current datetime
lastUpdateToSerialNumDict.lastUpdated = String.Format("{0:MM/dd/yyyy HH:mm:ss}", DateTime.Now);
// A list of serial numbers got from database
lastUpdateToSerialNumDict.serialNumbers = new List<string>();
lastUpdateToSerialNumDict.serialNumbers.Add("123456789");
string jsonRes = JsonConvert.SerializeObject(lastUpdateToSerialNumDict);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(jsonRes, Encoding.UTF8, "application/json");
return response;
}
// POST request to webServiceURL/version/devices/deviceLibraryIdentifier/registrations/passTypeIdentifier/serialNumber
[HttpPost]
public HttpResponseMessage RegisterDevice(string deviceLibraryIdentifier, string passTypeIdentifier, string serialNumber, [FromBody]HR.CardApi.Api.Models.DevicesPayload payload)
{
//ApiLog
System.IO.File.AppendAllText(@"ApiLog.txt", "devices/register was used");
System.IO.File.AppendAllText(@"D:\FGC-Microservice\HR.CardApi.DDC_31072019\HR.CardApi.Api\bin\ApiLog.txt", System.Environment.NewLine);
//ApiLog
System.IO.File.AppendAllText(@"data.txt", deviceLibraryIdentifier);
System.IO.File.AppendAllText(@"data.txt", System.Environment.NewLine);
System.IO.File.AppendAllText(@"data.txt", passTypeIdentifier);
System.IO.File.AppendAllText(@"data.txt", System.Environment.NewLine);
System.IO.File.AppendAllText(@"data.txt", serialNumber);
System.IO.File.AppendAllText(@"data.txt", System.Environment.NewLine);
System.IO.File.AppendAllText(@"data.txt", payload.pushToken);
System.IO.File.AppendAllText(@"data.txt", System.Environment.NewLine);
System.IO.File.AppendAllText(@"data.txt", System.Environment.NewLine);
System.Net.Http.HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
// DELETE request to webServiceURL/version/devices/deviceLibraryIdentifier/registrations/passTypeIdentifier/serialNumber
[HttpDelete]
public HttpResponseMessage UnRegisterDevice(string deviceLibraryIdentifier, string passTypeIdentifier, string serialNumber)
{
//Udpate Devices and Register table
System.Net.Http.HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
}
pass.json 个 pass.pkpass 个文件
{
"passTypeIdentifier": "SAME_AS_IN_KEYCHAIN",
"formatVersion": 1,
"serialNumber": "123456789",
"description": "CONFIDENTIAL",
"organizationName": "CONFIDENTIAL",
"teamIdentifier": "CONFIDENTIAL",
"voided": false,
"barcodes": [
{
"format": "PKBarcodeFormatPDF417",
"message": "1234587568464654",
"messageEncoding": "ISO-8859-1"
}
],
"storeCard": {
"headerFields": [],
"primaryFields": [],
"secondaryFields": [],
"auxiliaryFields": [
{
"key": "offer",
"label": "Initial Pass",
"value": "8979787645464654"
}
],
"backFields": [
{
"key": "CONFIDENTIAL",
"label": "CONFIDENTIAL",
"attributedValue": "CONFIDENTIAL",
"value": "CONFIDENTIAL"
}
]
},
"authenticationToken": "wxyzd7J8AlYYFPS0k0a0FfVFtq0ewzEfd",
"webServiceURL": "https://CONFIDENTIAL.com"
}
您可以提出任何与代码相关的问题。目前,我正在检查设备日志。如果我发现有用的东西,我会更新。
感谢您的宝贵时间。
我明白问题出在哪里了。有问题的代码是正确的。它没有错误。
当设备请求序列号时,API 返回 HTTP 404。这是因为我的通行证的 passTypeIdentifier 中包含点。就像 pass.acbcd.abcd。 我刚刚将我的 API 配置为允许 url 中的点,一切都开始正常工作。