dotnet-passbook icon indicating copy to clipboard operation
dotnet-passbook copied to clipboard

PKPass files won't open in Safari Mobile

Open devilb0x opened this issue 1 year ago • 1 comments

Passes generated with this tool can't be opened with Safari Mobile, but can be opened on a Mac laptop. Logs obtained from XCode don't show anything useful.

error	16:14:47.923573-0600	MobileSafari	PassBook Pass download failed: (null)

Passes created with @walletpass/pass-js work just fine. Both coupons pass all checks in the pkpass validator. https://pkpassvalidator.azurewebsites.net/

This is the pass.json generated with this tool. This coupon doesn't open.

{
  "semantics": {},
  "passTypeIdentifier": "redacted",
  "formatVersion": 1,
  "serialNumber": "637957668378982586",
  "description": "Buy one New 20oz Coke Orange Vanilla, get one free.",
  "organizationName": "Distributer Company, LLC",
  "teamIdentifier": "redacted",
  "sharingProhibited": false,
  "labelColor": "rgb(255,255,255)",
  "coupon": {
    "headerFields": [
      {
        "key": "header",
        "label": "Date",
        "value": "Placeholder"
      }
    ],
    "primaryFields": [
      {
        "key": "offer",
        "label": " ",
        "value": " "
      }
    ],
    "secondaryFields": [
      {
        "key": "details",
        "label": "OFFER",
        "value": "Buy one New 20oz Coke Orange Vanilla, get one free."
      }
    ],
    "auxiliaryFields": [
      {
        "key": "details",
        "label": "OFFER",
        "value": "Buy one New 20oz Coke Orange Vanilla, get one free."
      }
    ],
    "backFields": [
      {
        "key": "terms",
        "label": "Terms and Conditions",
        "value": "Valid at Participating locations.\n\nCoupon values are as specified on the coupon.\n\nLimit of one coupon per customer.\n\nCoupons are intended for single use only.\n\nEach coupon is valid for a limited time only and expires on the date specified in the offer.\n\nWe reserve the right to modify or cancel coupons at any time."
      },
      {
        "key": "signer",
        "label": "Developer",
        "value": " "
      }
    ]
  }
}

This is the pass.json generated with the pass-js library. This downloads and opens in Mobile Safari.

{
	"passTypeIdentifier": "redacted",
	"formatVersion": 1,
	"serialNumber": "121212",
	"organizationName": "redacted",
	"teamIdentifier": "redacted",
	"description": "No description",
	"labelColor": "rgb(255, 255, 255)",
	"coupon": {
		"backFields": [
			{
				"key": "terms",
				"label": "Terms and Conditions",
				"value": "Valid at Participating locations.\n\nCoupon values are as specified on the coupon.\n\nLimit of one coupon per customer.\n\nCoupons are intended for single use only.\n\nEach coupon is valid for a limited time only and expires on the date specified in the offer.\n\nWe reserve the right to modify or cancel coupons at any time."
			},
			{
				"key": "signer",
				"label": "Development",
				"value": " "
			}
		],
		"headerFields": [
			{
				"key": "date",
				"label": "Expiration",
				"dateStyle": "PKDateStyleShort",
				"timeStyle": "PKDateStyleShort",
				"value": "2022-08-10T06:00+00:00"
			}
		],
		"primaryFields": [
			{
				"key": "offer",
				"label": " ",
				"value": " "
			}
		],
		"secondaryFields": [
			{
				"key": "details",
				"label": "OFFER",
				"value": "Buy one New 20oz Coke Orange Vanilla, get one free."
			}
		]
	},
	"barcodes": [
		{
			"message": "https://google.com",
			"format": "PKBarcodeFormatQR",
			"messageEncoding": "iso-8859-1"
		}
	]
}

Here is the code I'm using to generate the pass.

string certsPath = AppDomain.CurrentDomain.BaseDirectory + "/Certs";

            PassGenerator generator = new PassGenerator();
            PassGeneratorRequest request = new PassGeneratorRequest();

            X509KeyStorageFlags flags = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable;

            request.PassbookCertificate = new X509Certificate2(
                certsPath + "/portal-coupon-cert.p12",
                _config.GetSection("AWSSettings")["CouponService"],
                flags);

            request.AppleWWDRCACertificate = new X509Certificate2(certsPath + "/AppleWWDRCAG6.cer");

            request.Style = PassStyle.Coupon;

            request.PassTypeIdentifier = "redacted";
            request.TeamIdentifier = "redacted";
            request.SerialNumber = DateTime.UtcNow.Ticks.ToString();
            request.Description = couponData.Offer;
            request.OrganizationName = couponData.Provider;

            request.BackgroundColor = couponData.BackgroundColor;
            request.LabelColor = couponData.LabelColor;
            request.ForegroundColor = couponData.LabelColor;

            request.AddHeaderField(new StandardField("header", "Date", "Placeholder"));

            request.AddPrimaryField(new StandardField("offer", " ", " "));
            request.AddSecondaryField(new StandardField("details", "OFFER", couponData.Offer));

            request.AddBackField(new StandardField("terms", "Terms and Conditions", couponData.CouponDisclaimer));
            request.AddBackField(new StandardField("signer", "Developer", " "));

            request.Images.Add(PassbookImage.Icon, images["iconSmall"]);
            request.Images.Add(PassbookImage.Icon2X, images["iconLarge"]);
            request.Images.Add(PassbookImage.Strip, images["stripSmall"]);
            request.Images.Add(PassbookImage.Strip2X, images["stripLarge"]);
            request.Images.Add(PassbookImage.Logo, images["logoSmall"]);
            request.Images.Add(PassbookImage.Logo2X, images["logoLarge"]);

            if (!string.IsNullOrWhiteSpace(couponData.BarcodeMessage))
            {
                switch (couponData.BarcodeType)
                {
                    case "pdf417":
                        request.AddBarcode(BarcodeType.PKBarcodeFormatPDF417, couponData.BarcodeMessage, "ISO-8859-1");
                        break;

                    case "azteccode":
                        request.AddBarcode(BarcodeType.PKBarcodeFormatAztec, couponData.BarcodeMessage, "ISO-8859-1");
                        break;

                    case "code_128":
                        request.AddBarcode(BarcodeType.PKBarcodeFormatCode128, couponData.BarcodeMessage, "ISO-8859-1");
                        break;
port
                    case "qrcode":
                    case "qr":
                    default:
                        request.AddBarcode(BarcodeType.PKBarcodeFormatQR, couponData.BarcodeMessage, "ISO-8859-1");
                        break;
                }
            }

            var generatedPass = generator.Generate(request);

Any help would be very much appreciated.

devilb0x avatar Aug 10 '22 23:08 devilb0x

In order to get more information from the iPhone logs, you can enable debug logging from the developer settings on your iPhone.

Typically when a pass doesn't open it's related to the certificates being invalid or missing keys/duplicate keys.

My pkpassvalidator is not comprehensive, so there are lots of things it's doesn't check.

Enable the debug logging and try again. I don't have access to a Mac at present, but if you send me the pass that isn't working, I can have a look next week.

tomasmcguinness avatar Aug 11 '22 05:08 tomasmcguinness

Hi, I'm just following up on this - could you send me the pass that is failing please?

tomasmcguinness avatar Sep 04 '22 08:09 tomasmcguinness

Sorry for the late reply. We managed to get this working. It was my fault, I failed to see that the MIME type was actually being set to application/vnd.apple.pkpasses instead of application/vnd.apple.pkpass. Thanks for your help.

devilb0x avatar Sep 07 '22 19:09 devilb0x

Marvelous

tomasmcguinness avatar Sep 07 '22 19:09 tomasmcguinness