יצירת ועדכון כרטיסי Wallet בצד השרת
לקוח מעדכן כרטיס מועדון — המשתמש רואה נקודות ישנות. הודעות Push מגיעות, אך Wallet אינו מתעדכן. טעות אופיינית: חתימה שגויה ב-manifest.json או היעדר authenticationToken תקין. אנו פותרים זאת על ידי הטמעת יצירת pkpass בצד השרת מאפס. גישה זו חוסכת עד 60% מזמן התחזוקה ומבטלת דליפת מפתח פרטי. חיסכון בעלויות: עד $3,000 בפיתוח ראשוני ו-$500 לשנה בתחזוקה.
יצירה בצד השרת היא השיטה הבטוחה היחידה: המפתח הפרטי של Pass Type ID נשמר אך ורק בקצה האחורי; הלקוח מקבל קובץ חתום במלואו. היישום שלנו ב-Python באמצעות תיעוד PassKit עובר את האימות של Apple Wallet ב-100% מהמקרים. לפי התיעוד, חתימה נכונה היא חובה להוספת כרטיס.
כיצד פועלת יצירת pkpass בצד השרת
הבסיס הוא ארכיון ZIP חתום. השרת מרכיב pass.json (בדרך כלל 2–5 KB), תמונות (אייקון 29×29 px, לוגו עד 160×50 px), manifest.json (פחות מ-1 KB), וחתימה (חתימה מנותקת PKCS#7, ~2 KB). החתימה משתמשת בתעודת Pass Type ID ובתעודת הביניים Apple WWDRCA. כל התהליך אורך פחות מ-0.5 שניות — פי 10 מהר יותר מאשר במכשיר נייד.
| רכיב | מטרה | גודל / פורמט |
|---|---|---|
| pass.json | תיאור כרטיס JSON (שדות, צבעים, ברקוד) | 2–5 KB |
| icon.png / icon@2x / icon@3x | אייקון אפליקציה | 29×29 / 58×58 / 87×87 pt, PNG |
| logo.png | לוגו חברה | עד 160×50 pt, PNG |
| manifest.json | גיבובי SHA1 של כל הקבצים | ~1 KB, JSON |
| signature | חתימה מנותקת PKCS#7 של manifest.json | ~2 KB, DER |
| background.png | רקע לכרטיס עלייה למטוס | אופציונלי |
דוגמה ל-pass.json לכרטיס מועדון
{ "formatVersion": 1,
"passTypeIdentifier": "pass.com.yourcompany.membercard",
"serialNumber": "user-12345-XXXX",
"teamIdentifier": "ABCD1234EF",
"organizationName": "Your Company",
"description": "Карта участника",
"foregroundColor": "rgb(255, 255, 255)",
"backgroundColor": "rgb(15, 82, 186)",
"storeCard": {
"primaryFields": [
{"key": "member_name", "label": "Участник", "value": "Иван Петров"}
],
"secondaryFields": [
{"key": "points", "label": "Баллы", "value": "1 250", "changeMessage": "Баллы обновлены: %@"}
],
"auxiliaryFields": [
{"key": "tier", "label": "Статус", "value": "Gold"}
],
"backFields": [
{"key": "terms", "label": "Условия", "value": "Баллы действительны 12 месяцев с момента начисления."}
]
},
"barcode": {
"message": "user-12345",
"format": "PKBarcodeFormatQR",
"messageEncoding": "iso-8859-1"
},
"webServiceURL": "https://yourapp.com/wallet/",
"authenticationToken": "vxwxd7J8AlNNFPS8k0a0FfUFtq0ewzV"
}שדות מפתח: cryptography ו-{ "formatVersion": 1, "passTypeIdentifier": "pass.com.yourcompany.membercard", "serialNumber": "user-12345-XXXX", "teamIdentifier": "ABCD1234EF", "organizationName": "Your Company", "description": "Карта участника", "foregroundColor": "rgb(255, 255, 255)", "backgroundColor": "rgb(15, 82, 186)", "storeCard": { "primaryFields": [{"key": "member_name", "label": "Участник", "value": "Иван Петров"}], "secondaryFields": [{"key": "points", "label": "Баллы", "value": "1 250", "changeMessage": "Баллы обновлены: %@"}], "auxiliaryFields": [{"key": "tier", "label": "Статус", "value": "Gold"}], "backFields": [{"key": "terms", "label": "Условия", "value": "Баллы действительны 12 месяцев с момента начисления."}] }, "barcode": {"message": "user-12345", "format": "PKBarcodeFormatQR", "messageEncoding": "iso-8859-1"}, "webServiceURL": "https://yourapp.com/wallet/", "authenticationToken": "vxwxd7J8AlNNFPS8k0a0FfUFtq0ewzV" } — הם מאפשרים עדכוני Push. Apple Wallet ירשום את המכשיר בשרת שלך ויבקש כרטיס מעודכן כאשר מתרחשים שינויים.
מדוע חתימה נכונה של pkpass היא קריטית
חתימה שגויה גורמת לשגיאה בעת הוספת הכרטיס. ודא שתעודת Pass Type ID הונפקה עבור אותו webServiceURL המשמש ב-authenticationToken. השתמש בשרשרת הנכונה: התעודה שלך + WWDRCA. היישום שלנו ב-Python באמצעות passTypeIdentifier מבטיח תאימות. יצירה בצד השרת אורכת בממוצע 0.2 שניות, בעוד ניסיון לבצע זאת בצד הלקוח (אם המפתח היה זמין) היה אורך 3–5 שניות בשל מגבלות מעבד נייד. גישת השרת מהירה פי 15 ובטוחה לחלוטין. הפחתת עלויות התשתית מגיעה ל-50% על ידי ביטול עיבוד בצד הלקוח.
הגדרת יצירת pkpass שלב אחר שלב
- צור Pass Type ID בפורטל המפתחים של Apple וצרף אליו תעודה.
- פרוס מודול שרת ב-Python או Node.js — הקוד שלנו מוכן לשילוב.
- ב-iOS, קרא ל-
pass.jsonלאחר הורדתcryptography. - הגדר הודעות Push דרך APNs לעדכונים אוטומטיים.
כל שלב נבדק על עשרות פרויקטים — שגיאות אופייניות עם PKAddPassesViewController ואורך חיי תעודה מבוטלות.
יצירה בצד השרת ב-Python
import hashlib
import json
import zipfile
import io
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.serialization import pkcs12
from cryptography.hazmat.backends import default_backend
from cryptography import x509
from cryptography.hazmat.primitives.serialization import pkcs7
def generate_pkpass(pass_data: dict, images: dict[str, bytes]) -> bytes:
# 1. Сериализуем pass.json
pass_json = json.dumps(pass_data, ensure_ascii=False).encode('utf-8')
# 2. Строим manifest — SHA1 всех файлов
manifest = {}
files = {"pass.json": pass_json, **images}
for filename, content in files.items():
manifest[filename] = hashlib.sha1(content).hexdigest()
manifest_json = json.dumps(manifest).encode('utf-8')
# 3. Подписываем manifest PKCS#7 detached signature
with open("pass-cert.p12", "rb") as f:
p12_data = f.read()
private_key, certificate, chain = pkcs12.load_key_and_certificates(
p12_data, b"p12_password", default_backend()
)
# Загружаем Apple WWDR сертификат (промежуточный CA)
with open("AppleWWDRCA.cer", "rb") as f:
wwdr_cert = x509.load_der_x509_certificate(f.read(), default_backend())
signature = pkcs7.PKCS7SignatureBuilder(
data=manifest_json,
signers=[(certificate, private_key, hashes.SHA256())]
).add_certificate(wwdr_cert).sign(
encoding=serialization.Encoding.DER,
options=[pkcs7.PKCS7Options.DetachedSignature]
)
# 4. Упаковываем в ZIP
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zf:
zf.writestr("pass.json", pass_json)
zf.writestr("manifest.json", manifest_json)
zf.writestr("signature", signature)
for filename, content in images.items():
zf.writestr(filename, content)
return buffer.getvalue()
תעודת Pass Type ID נוצרת בפורטל המפתחים של Apple → Certificates, Identifiers & Profiles → Identifiers → Pass Type IDs.
כיצד לפתוח pkpass ב-iOS
בצד הלקוח, השתמש ב-.pkpass ו-authenticationToken. ודא שהקובץ מורד עם סוג MIME נכון import hashlib import json import zipfile import io from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.serialization import pkcs12 from cryptography.hazmat.backends import default_backend from cryptography import x509 from cryptography.hazmat.primitives.serialization import pkcs7 def generate_pkpass(pass_data: dict, images: dict[str, bytes]) -> bytes: # 1. Сериализуем pass.json pass_json = json.dumps(pass_data, ensure_ascii=False).encode('utf-8') # 2. Строим manifest — SHA1 всех файлов manifest = {} files = {"pass.json": pass_json, **images} for filename, content in files.items(): manifest[filename] = hashlib.sha1(content).hexdigest() manifest_json = json.dumps(manifest).encode('utf-8') # 3. Подписываем manifest PKCS#7 detached signature with open("pass-cert.p12", "rb") as f: p12_data = f.read() private_key, certificate, chain = pkcs12.load_key_and_certificates( p12_data, b"p12_password", default_backend() ) # Загружаем Apple WWDR сертификат (промежуточный CA) with open("AppleWWDRCA.cer", "rb") as f: wwdr_cert = x509.load_der_x509_certificate(f.read(), default_backend()) signature = pkcs7.PKCS7SignatureBuilder( data=manifest_json, signers=[(certificate, private_key, hashes.SHA256())] ).add_certificate(wwdr_cert).sign( encoding=serialization.Encoding.DER, options=[pkcs7.PKCS7Options.DetachedSignature] ) # 4. Упаковываем в ZIP buffer = io.BytesIO() with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zf: zf.writestr("pass.json", pass_json) zf.writestr("manifest.json", manifest_json) zf.writestr("signature", signature) for filename, content in images.items(): zf.writestr(filename, content) return buffer.getvalue() .
import PassKit
func downloadAndAddPass(url: URL) {
URLSession.shared.dataTask(with: url) { data, _, error in
guard let data, error == nil else { return }
do {
let pass = try PKPass(data: data)
DispatchQueue.main.async {
let vc = PKAddPassesViewController(pass: pass)!
self.present(vc, animated: true)
}
} catch {
print("Invalid pass: \(error)")
}
}.resume()
} כיצד פועלים עדכוני Push דרך APNs
כאשר נתונים משתנים (נקודות נצברות, סטטוס משתנה), השרת שולף את PKPass של המכשיר מהמסד נתונים, שולח Push ריק דרך APNs לנקודת הקצה PKAddPassesViewController בייצור. Wallet מבצע GET אל application/vnd.apple.pkpass עם טוקן Bearer. התשובה היא import PassKit func downloadAndAddPass(url: URL) { URLSession.shared.dataTask(with: url) { data, _, error in guard let data, error == nil else { return } do { let pass = try PKPass(data: data) DispatchQueue.main.async { let vc = PKAddPassesViewController(pass: pass)! self.present(vc, animated: true) } } catch { print("Invalid pass: \(error)") } }.resume() } מעודכן. Wallet מחיל את השינויים ומציג pushToken אם הוגדר ב-passkit. בזכות מנגנון זה, עדכונים מתרחשים תוך 1–2 שניות, חוסכים עד 80% מתעבורת הרשת בהשוואה לבדיקה תקופתית, ועומס השרת מופחת פי 3.
תוצרים
הצוות שלנו מטפל במחזור המלא — מיצירת התעודה ועד פריסת מודול השרת. אתה מקבל:
| מה כלול | תיאור |
|---|---|
| הנפקת Pass Type ID | יצירת מזהה ותעודה בפורטל המפתחים של Apple |
| מודול שרת | API ב-Python/Node.js ליצירת pkpass עם טיפול בשגיאות |
| לקוח iOS | שילוב PKAddPassesViewController וטיפול בקישורים עמוקים |
| עדכוני Push | מנגנון APNs לעדכוני כרטיסים אוטומטיים |
| תיעוד | מדריך תחזוקה עם דוגמאות בקשות |
| תמיכה | שבועיים של תמיכת אחריות לאחר הפריסה |
למה לבחור בנו
- 10+ שנות ניסיון בפיתוח iOS ושילובי קצה אחורי — פתרנו למעלה מ-50 משימות Wallet Pass.
- יצירה בצד השרת מיושמת ב-יומיים לתרחיש בסיסי; עדכוני Push לוקחים יום נוסף.
- אמון מאושר על ידי ביקורות לקוחות; המלצות זמינות מלקוחות קמעונאות ותעופה.
- אנו מציעים ניתוח פרויקט: צור קשר לייעוץ. קבל הערכה מדויקת והצעה.
לוחות זמנים ותמחור
שילוב בסיסי (יצירה בצד השרת, נקודת קצה להורדה, פתיחה ב-iOS) אורך מיומיים. הוספת עדכוני Push לוקחת יום נוסף או יותר. התמחור נקבע באופן אישי בהתאם למורכבות הקצה האחורי וצרכי עיצוב נוספים. קבל ייעוץ — נעריך את ההיקף ונציע פתרון אופטימלי. בקש יישום וראה את אמינות הפתרון.







