tomasmcguinness / dotnet-passbook

A .Net Library for generating Apple Passbook (Wallet) files for iOS. Please get involved by creating pull requests and opening issues!
MIT License
319 stars 117 forks source link

PKPass files won't open in Safari Mobile #168

Closed devilb0x closed 2 years ago

devilb0x commented 2 years ago

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.

tomasmcguinness commented 2 years ago

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 commented 2 years ago

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

devilb0x commented 2 years ago

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.

tomasmcguinness commented 2 years ago

Marvelous