
內容摘要
.pkpass 是一個 ZIP,內含 pass.json、記錄各檔 SHA-1 的 manifest、PKCS#7 簽章與圖片。只要有一個位元組對不上,Wallet 就會直接拒收,而且不會給出有用訊息。
Apple Wallet 票卡(.pkpass)是一個已簽章的 ZIP 壓縮檔,不是文件格式。Wallet 不會針對無效票卡顯示警告,而是直接拒絕加入,所以多數人第一次接觸這個格式,就是盯著一張怎麼都開不了的票卡。本文會從四個必要部分把票卡建起來,再說明那些會導致無聲退件的失敗模式。
Apple 也提供 Pass Designer,可用視覺化方式設計票卡,你可以用它先做版面原型。但當票卡要由伺服器產生時,你仍得自己組出這個壓縮檔。
01.pkpass 檔案實際上由什麼組成
把任何一張可用的票卡改名為 .zip 解開,你會看到這些部分:
| 檔案 | 是否必要 | 用途 |
|---|---|---|
pass.json | 是 | 票卡定義:識別碼、樣式、顏色、條碼、欄位 |
manifest.json | 是 | 套件內其他每個檔案的 SHA-1 雜湊 |
signature | 是 | 對 manifest.json 的 PKCS#7 分離式簽章 |
icon.png、icon@2x.png | 是 | Wallet 與通知中顯示的小圖標 |
logo.png、strip.png、縮圖 | 依樣式 | 選用美術圖,尺寸必須符合 Apple 規範 |
manifest 是完整性檢查層,而且 Apple 的建置步驟刻意讓它保持單純:一個把每個來源檔的路徑對應到 SHA-1 十六進位摘要的扁平 JSON 物件。它永遠不包含自己或簽章,也不該把 .DS_Store 之類的中介資料檔列進去。
{
"icon.png": "6c4f1b2a4e2b7d3c9a5f8e1d0b6a3c2e4f5a6b7c",
"logo.png": "b1d2c3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0",
"pass.json": "9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c"
}
manifest 必須與 ZIP 完全一致
如果
manifest.json列出logo@2x.png但檔案不存在,或 ZIP 裡有檔案卻沒列進 manifest,簽章驗證就會失敗,Wallet 便會拒收。每次更動圖片後都要重新產生 manifest。
02為五種票卡樣式建立 pass.json
每張票卡都有一組共用欄位,然後宣告唯一一種樣式。共用欄位是 formatVersion(固定為 1)、passTypeIdentifier、serialNumber、teamIdentifier、organizationName 與 description。若你的伺服器要推送更新,還需要 webServiceURL 與 authenticationToken。
樣式字典只能是 generic、storeCard、coupon、eventTicket 或 boardingPass 其中之一。每種樣式提供不同的欄位區域:登機證有固定位置的 primaryFields、secondaryFields、auxiliaryFields 與 backFields,一般票卡則用相同區域承載不同用途。
欄位數量有上限。一般而言,一張票卡最多可有 3 個 header 欄位、1 個 primary 欄位、4 個 secondary 欄位與 4 個 auxiliary 欄位;登機證可用 2 個 primary 與 5 個 auxiliary,而優惠券、會員卡與方形條碼的一般票卡則共用 4 個 secondary 與 auxiliary 欄位。(PassKit 指南)
{
"formatVersion": 1,
"passTypeIdentifier": "pass.com.example.loyalty",
"serialNumber": "member-8842",
"teamIdentifier": "ABCDE12345",
"organizationName": "Example Coffee",
"description": "Example Coffee loyalty card",
"backgroundColor": "rgb(67, 56, 202)",
"foregroundColor": "rgb(255, 255, 255)",
"labelColor": "rgb(224, 231, 255)",
"storeCard": {
"primaryFields": [
{ "key": "balance", "label": "Balance", "value": 12.5, "currencyCode": "USD" }
],
"secondaryFields": [
{ "key": "tier", "label": "Tier", "value": "Gold" }
]
},
"barcodes": [
{ "format": "PKBarcodeFormatQR", "message": "member-8842", "messageEncoding": "iso-8859-1" }
]
}
有兩個細節特別容易出錯。顏色使用 rgb(r, g, b) 字串形式,不是十六進位、也不是陣列。而 serialNumber 是這張票卡的身分,若把不同會員重複使用同一個序號,推送更新時就會覆蓋到錯的卡片。
03產生 manifest 與 PKCS#7 簽章
簽章是無法省略的一步。你需要從 Apple 開發者帳號取得 Pass Type ID 憑證與其私鑰,再加上 Apple 的 WWDR 中繼憑證。
- 對除了 manifest 與 signature 之外的每個檔案計算雜湊。 在票卡目錄內執行:
for f in $(find . -type f ! -name 'manifest.json' ! -name 'signature' -maxdepth 1 | sed 's|^\./||'); do
printf ' "%s": "%s",\n' "$f" "$(shasum -a 1 "$f" | cut -d' ' -f1)"
done
- 組成
manifest.json。 必須是有效 JSON,最後一項後面不能有逗號——上面的循環會刻意留下尾逗號,請自行移除,或改用腳本產生檔案。 - 對 manifest 簽章,而不是對個別檔案:
openssl smime -binary -sign \
-certfile WWDR.pem \
-signer passcert.pem \
-inkey passkey.pem \
-in manifest.json \
-out signature \
-outform DER
- 壓縮目錄,不要多一層父文件夾,也不要帶入 macOS 中介資料:
zip -r example.pkpass . -x '.*' -x '__MACOSX' -x '.*/'
- 出貨前先驗證。 簽章必須能對上你實際打包的 manifest:
openssl smime -verify -in signature -inform DER -content manifest.json -noverify
若這個指令印出 Verification successful,密碼學的部分就完成了。
04加入 Wallet 預期的圖片並設定尺寸
Wallet 會先縮放再裁切你提供的圖,所以真正要對準的是每張圖獲配的空間,單位是點。不符獲配空間的圖會被縮放後裁切,而 icon.png 是必填。
| 圖片 | 獲配空間 | 說明 |
|---|---|---|
icon.png | 29 × 29 點 | 必填;鎖定畫面與郵件附件也用它 |
logo.png | 160 × 50 點 | 通常會比獲配空間窄 |
strip.png | 375 × 123 點 | 會員卡與優惠券為 375 × 144,活動門票為 375 × 98 |
thumbnail.png | 90 × 90 點 | 長寬比維持在 2:3 至 3:2 之間 |
background.png | 180 × 220 點 | 四邊會略為裁切並模糊 |
非 Retina 裝置上 1 點等於 1 像素,Retina 裝置上則是每個點 2 或 3 個像素,所以要一併提供 @2x 與 @3x 版本。(PassKit 指南)
最後才重新產生 manifest
先改圖片,再重建
manifest.json,接著簽章,最後壓縮。順序顛倒會讓 manifest 留下過期雜湊,做出看起來完成、實際驗證失敗的票卡。
05會無聲失敗的條碼規則
條碼是票卡從設計變成實用的分界。條碼掃描器與相關軟體通常預期 ISO 8859-1(Latin-1)或 Windows-1252,而大多數系統對 Unicode 支援很差,所以 message 裡出現 emoji 或非拉丁字元,就是最典型的無聲失敗。請讓條碼內容維持 ASCII,把給人看的文字放進欄位。
| 現象 | 原因 | 解法 |
|---|---|---|
| 票卡加得進去但條碼空白 | barcodes 陣列缺少或為空 | 至少加入一筆有效 format |
| 條碼顯示亂碼 | message 不是 ISO 8859-1 或 Windows-1252 | message 用 ASCII,文字移到欄位 |
| Apple Watch 上沒有條碼 | 只放了 Code 128,而 watchOS 不支援 | 補一筆 QR 或 PDF 417 作為後備 |
| 舊 iPhone 顯示不出條碼 | 只有舊式 barcode 鍵 | 一併輸出 barcodes;iOS 9 起改用陣列 |
Wallet 支援 QR、PDF 417、Aztec、Code 128、Code 39、Codabar、EAN 13 與 ITF,PassKit 會採用陣列中第一個支援的項目。QR、PDF 417、Aztec 與 Code 128 在 iOS 9 或之後都有效,這四種不需要後備。手機畫面能給的面積很小,QR 是最安全的選擇。(條碼、PassKit 指南)
payload 越短越穩
每種格式都有硬性上限,而真實掃描環境會再把上限壓低——指南舉的例子正是在光線不足的環境下,用便宜的掃描器讀 Apple Watch 上的條碼。請用場地實際使用的硬體測試。
06先在實機測試,再規劃更新
Apple 記載的測試方式是把票卡拖進模擬器中的 iPhone,票卡有效時會出現加入對話框;對外推出前仍要用實機跑完整個加入 Wallet 流程。用 AirDrop 傳檔,或從 HTTPS 端點以 application/vnd.apple.pkpass MIME 類型提供,Safari 才會出現加入按鈕。(Technical Note TN2302)
若票卡在核發後還會變動——例如餘額、登機門、登機時間——你不會在裝置上重建它。你要在 pass.json 加上 webServiceURL 與 authenticationToken,裝置會向你的伺服器註冊並提供推送權杖,之後你的伺服器發送推送通知,裝置就會取回更新後的票卡。這也是 authenticationToken 必須又長又隨機的原因:它是驗證每一次更新端點呼叫的共用密鑰。
07常見錯誤與修正
| 錯誤 | 可能原因 | 修正 |
|---|---|---|
| 只顯示「無法加入票卡」 | manifest 與簽章不符 | 重建 manifest、重新簽章、重新壓縮 |
| 簽章通過但 Wallet 拒收 | passTypeIdentifier 與憑證不符 | 使用入口網站上完全一致的 Pass Type ID |
| 票卡沒有套用樣式 | 背景/前景色用了十六進位字串 | 改用 rgb(r, g, b) 字串 |
| 更新永遠不送達 | serialNumber 錯誤或推送權杖過期 | 每張票卡使用唯一且穩定的序號 |
幾乎所有失敗都指向同一個模式:Wallet 把票卡視為已簽章的產物,所以任何位元組變動——包括重新存一次圖片——都會讓簽章失效。寫一個一次完成「重建 manifest、簽章、壓縮」的腳本,你就不必再手動追雜湊。
08資料來源
- Building a Pass — https://developer.apple.com/documentation/walletpasses/building-a-pass (查閱日期:2026-10-09)
- Creating a Pass with Pass Designer — https://developer.apple.com/documentation/walletpasses/creating-a-pass-with-pass-designer (查閱日期:2026-10-09)
- Pass — https://developer.apple.com/documentation/walletpasses/pass (查閱日期:2026-10-09)
- PassKit Programming Guide: Creating Passes — https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/PassKit_PG/Creating.html (查閱日期:2026-10-09)
- Barcodes — https://developer.apple.com/documentation/walletpasses/pass/barcodes (查閱日期:2026-10-09)
- Technical Note TN2302: Passbook FAQ — https://developer.apple.com/library/archive/technotes/tn2302/_index.html (查閱日期:2026-10-09)
- Adding a Web Service to Update Passes — https://developer.apple.com/documentation/walletpasses/adding-a-web-service-to-update-passes (查閱日期:2026-10-09)
先驗證並格式化 pass.json 再雜湊——那裡多一個逗號,整張票卡就毀了。
JSON 多功能工作區常見問題
為什麼我的 .pkpass 加不進 Apple Wallet?
十次有九次是簽章與 manifest 對不上,或 manifest 列了 ZIP 裡不存在的檔案。這兩種情況 Wallet 都會默默拒收。
沒有 Apple 開發者帳號可以建立 Wallet 票卡嗎?
不行。簽章需要 Apple 開發者帳號核發的 Pass Type ID 憑證。你可以先做 JSON 與圖片,但 Wallet 不會接受未簽章的票卡。
manifest 用的是 SHA-1 還是 SHA-256?
manifest 格式是對套件內每個檔案計算 SHA-1,這與 Git 物件雜湊該用 SHA-1 還是 SHA-256 的討論無關。
為什麼條碼在 Apple Watch 上不見了?
watchOS 不支援 Code 128。如果 barcodes 陣列裡只有 Code 128,手錶上就完全不會顯示條碼,請補一筆 QR 或 PDF 417 作為後備。
為什麼我的條碼掃不出來?
通常是內容與掃描器的預期不符:條碼硬體一般讀 ISO 8859-1 或 Windows-1252,而系統對 Unicode 支援很差。請讓 message 維持 ASCII,並把給人看的文字移到欄位。
相關文章
- 如何格式化、驗證與壓縮 JSON
pass.json 是嚴格 JSON,會弄壞 API 載荷的規則同樣會弄壞票卡。
- 做出每次都掃得過的 QR 碼
票卡條碼的容錯率、靜默區與尺寸設定。
相關工具
- JSON 格式化工具
美化並重新縮排 pass.json。
- JSON 驗證工具
在簽署前抓出多餘的逗號。
- Base64 編碼/解碼
檢查編碼後的內容與條碼資料。