Pass converter is a tool to convert passes for different wallet apps from one wallet's format to another. Currently, this project supports the following wallet platforms:
- Google Wallet (the payload field of a JWT, as a
.json
file) - Apple Wallet (a
.pkpass
file)
The tool is built with JavaScript/Node.js and can run as a web service accepting a pass in a POST
request, returning the converted pass as a response, or as a command-line tool for converting pass files locally.
Note: This project covers the server-side implementation for creating passes, it does not deal with any front-end concerns such as buttons for saving passes to wallets. For further guidance on this, please consult the branding and usage guidelines for each wallet platform.
- Supported pass types
- Setup
- Usage
- Pass file formats
- Configuration
- External dependencies
- Hints for Google passes
- Updatable Passes
- Troubleshooting
The following table shows the mapping of pass types between each supported wallet platform.
Type | Google Wallet | Apple Wallet |
---|---|---|
Boarding pass | FlightObject |
Pass.BoardingPass (PKTransitTypeAir ) |
Transit pass | TransitObject |
Pass.BoardingPass (other types) |
Offer/Coupon | OfferObject |
Pass.Coupon |
Event ticket | EventTicketObject |
Pass.EventTicket |
Loyalty card | LoyaltyObject |
Pass.StoreCard |
Generic | GenericObject |
Pass.Generic |
- Install Node.js/NPM
- Download or clone this repository
- Install dependencies using
npm install .
node app.js demo
Provides a demo web page to test converting passes, which runs on http://localhost:3000 by default.
node app.js <pass input path> <pass output path>
Converts passes locally. If the output path is ommitted, the converter will output JSON to the terminal (for pkpass files, this will be the contents of pass.json).
node app.js
The web service expects a POST
request to the URL /convert/
with multipart/form-data
encoding. The request should include a single pass file. Updating passes is also supported via PATCH
requests to the same /convert/
URL. See the Updatable Passes section for more detail.
The web service must have some form of authentication implemented with an upstream web server (like Apache or Nginx). When configuring this in the upstream server, you must define a HTTP header in the request to the web service that the upstream web server will send. The name of the HTTP header must then be defined using the config.js
variable authHeader
. Requests to the converter are then restricted to requests containing the HTTP header.
The request body must contain a single pass file, for one of the supported types. See next section Pass file formats for an example request.
When converting to a Google Wallet pass, the pass converter will make additional API calls to Google Wallet to create the pass, if it is too large to be embedded directly in the "Save to Google Wallet" URL.
The response format will depend on the destinaton platform of the pass.
Target platform | Response type | Example |
---|---|---|
Google Wallet | A 302 redirect to the URL for saving the pass to Google Wallet. | https://pay.google.com/gp/v/save/{token} |
Apple Wallet | The PKPass file will be returned as a binary HTTP response. | See PKPass in the Apple developer documentation |
Google Wallet passes are defined by classes and objects which are created either via the Google Wallet APIs, using JWTs, or in the Google Pay & Wallet Console.
To learn more about the relationship between pass classes and objects, see How classes and objects work.
The pass converter defines a Google Wallet pass as a JSON file containing the value of the payload
field of a JWT. The payload must include the pass class and object.
Here is a minimal example JWT payload that defines the pass class and pass object to be created:
{
"offerClasses": [
{
"reviewStatus": "UNDER_REVIEW",
"issuerName": "Google",
"redemptionChannel": "BOTH",
"provider": "Google Developers",
"title": "An open source project"
}
],
"offerObjects": [
{
"barcode": {
"type": "qrCode",
"value": "123456789"
},
"hexBackgroundColor": "#ce8c35",
"state": "ACTIVE"
}
]
}
Important: JWT payloads use .json
as their extension, e.g., mypass.json
.
PKPass
files are signed, compressed archives containing metadata, media, and other pass data. For more information on pass structure, see Creating the Source for a Pass.
Configuration is implemented via a config.js
file. You can define the path to your config.js
file using the PASS_CONVERTER_CONFIG_PATH
environment variable, otherwise the config.js
file found in the root of this project is used.
The following variables are defined in the config.js
file. Most of these are covered in more detail in the External dependencies section next.
config.js variable |
Description | Example |
---|---|---|
googleServiceAccountJsonPath |
Path to Google service account JSON file | /path/to/file.json |
googleIssuerId |
Issuer ID for Google Wallet APIs | 1234567890123456789 |
googleStorageBucket |
Google Cloud Storage bucket name (see Image hosting) | my-bucket-name |
pkPassDefaultIconUrl |
The URL to an image file to use for the PKPass icon/logo when none available in source pass |
https://link/to/icon.png |
pkPassPassTypeId |
Apple pass type ID | .example-company.passes.ticket.event-4631A. |
pkPassTeamId |
Apple team ID | your-team-id |
pkPassSigningKeyPath |
Path to your private key for signing PKPass files |
/path/to/key.pem |
pkPassSigningCertPath |
Path to your certificate for signing PKPass files |
/path/to/cert.pem |
pkPassWwdrCertPath |
Path to the Apple WWDR certificate for signing PKPass files |
/path/to/wwdr.pem |
emptyValue |
A default value to use for missing fields | N/A |
defaultOrgName |
Default organization/issuer to use when none available in source pass | My company name |
defaultLanguage |
The default language to use in Google Wallet passes (PKPass files with translations do not define the default language) |
en |
authHeader |
The HTTP header name your upstream web server will send to the converter when requests are authenticated (see Authentication) | Authorization |
bindHost |
The HTTP host to bind the converter to when running as a web service | 127.0.0.1 |
bindPort |
The HTTP port to bind the converter to when running as a web service | 3000 |
apn |
Config for Apple Push Notifications (see node-apn documentation) | {"cert": "cert.pem", "key": "key.pem"} |
database |
Config for database, (see typeorm documentation) | {"type": "sqlite", "database": "database.sqlite"} |
hints |
(Mapping of field name hints, see Hints for Google passes) | {"event.name": ""} |
When running as a command-line tool, you can convert passes to Google Wallet JWT payloads without any external dependencies. However, when running the tool as a web service, you will need to do the following:
- Create a Google Cloud project
- Follow the prerequisites for using the Google Wallet APIs (specifically, steps 1 through 4)
Once complete, you should have a service account JSON file and Google Wallet Issuer ID. These must then be configured as the googleServiceAccountJsonPath
and googleIssuerId
config.js
variables, respectively.
Images in Google Wallet passes are referenced by URLs. Thus, they must be hosted and available to Google when creating pass classes and objects. When this project is run as a web service, it will automatically host images found during conversion to Google Wallet passes.
You can also use Google Cloud Storage to host the images. This has the added benefit of being able to restrict access to the service account mentioned earlier. To use Cloud Storage, you will need to follow the below steps.
- Create a bucket
- Use Cloud Identity and Access Management (Cloud IAM) to give your service account read/write access
- Set the
googleStorageBucket
config.js
variable to the name of the bucket you created
Note: You can still use the converter as a command-line tool to convert passes locally without configuring image hosting, however the resulting Google passes will not be usable without valid image URLs.
To create valid PKPass
files (they are cryptographically signed and viewable on Apple devices), you will need to follow the steps below.
Note: Omitting these steps and converting to unsigned Apple passes is also supported, however the resulting PKPass
file will not be readable by an Apple device. In this case you may use the converter to create unsigned passes, then perform the signing in your own application separately.
- Create an Apple Developer Program account, if you do not have one already, and identify your Team ID
- Create a pass type identifier
- Create a certificate signing request (CSR)
- Generate and download the signing certificate
- Import the signing certificate into the OSX keychain, and export it as certificates.p12
- Download one of the Apple Worldwide Developer Relations (WWDR) certificates
- Import the WWDR root certificate into the OSX keychain, and export it as wwdr.pem
- Install OpenSSL (required for these setup steps, and also when running the pass converter)
- Extract the certifcate from the certificates.p12 file with openssl:
openssl pkcs12 -in certificates.p12 -clcerts -nokeys -out mycert.pem
- Extract the key from the certificates.p12 file with openssl:
openssl pkcs12 -in certificates.p12 -nocerts -nodes -out mykey.pem
- Set the following
config.js
variables:
config.js key |
Value |
---|---|
pkPassTeamId |
Your Apple team ID (from step 1) |
pkPassPassTypeId |
The pass type identifier (from step 2) |
pkPassWwdrCertPath |
The path to the converted Apple WWDR certificate (wwdr.pem from step 7) |
pkPassSigningCertPath |
The path to your signing certificate (mycert.pem from step 9) |
pkPassSigningKeyPath |
The path to your private key (mykey.pem from step 10) |
Google Wallet passes often have specific fields dedicated to specific information (such as a flight number in a boarding pass), while PKPass
files may specify pass data more arbitrarily using PassFields
(e.g. auxilaryFields
, backFields
, and headerFields
).
In order to accommodate for this, you should configure the hints
key in the config.json
file. Here you can specify which PassFields
properties map to which Google Wallet pass properties.
The following hints are currently supported.
Pass type | Hint name | Description | Google Wallet pass property |
---|---|---|---|
Events | event.name |
Name of the event | EventClass.eventName |
Flights | flight.passenger |
Passenger name | FlightObject.passengerName |
flight.seatNumber |
Seat number | FlightObject.boardingAndSeatingInfo.seatNumber |
|
flight.seatClass |
Seat class | FlightObject.boardingAndSeatingInfo.seatClass |
|
flight.gate |
Gate number | FlightClass.origin.gate |
|
flight.originCode |
Origin airport code | FlightClass.origin.airportIataCode |
|
flight.destinationCode |
Destination airport code | FlightClass.destination.airportIataCode |
|
flight.flightNumber |
Flight number | FlightClass.flightHeader.flightNumber |
|
flight.date |
Departing date | FlightClass.localScheduledDepartureDateTime |
|
flight.time |
Departing time | FlightClass.localScheduledDepartureDateTime |
|
flight.confirmationCode |
Booking/confirmation code | FlightObject.reservationInfo |
|
Loyalty cards | loyalty.primaryPoints |
Primary points balance | LoyaltyObject.loyaltyPoints |
loyalty.secondaryPoints |
Secondary points balance | LoyaltyObject.secondaryLoyaltyPoints |
|
Transit passes | transit.originName |
Origin name | TransitObject.ticketLeg.originName |
transit.originDate |
Departing date | TransitObject.ticketLeg.departureDateTime |
|
transit.originTime |
Departing time | TransitObject.ticketLeg.departureDateTime |
|
transit.destinationName |
Destination name | TransitObject.ticketLeg.destinationName |
|
transit.destinationDate |
Arriving date | TransitObject.ticketLeg.arrivalDateTime |
|
transit.destinationTime |
Arriving time | TransitObject.ticketLeg.arrivalDateTime |
Updating passes is supported via PATCH
requests to the /convert/
URL. These requests should contain a single pass file containing the content to use when updating an existing pass. The pass file should contain a valid identifier (eg id
for a Google Pass, or serialNumber
for a PKPass) that references the pass to update.
Following is the behavior for each of the formats:
When a PKPass
file is sent in the PATCH
request, it is converted to a Google Wallet pass and updated via the Google Wallet API.
When a Google Wallet pass (.json) file is sent in the PATCH
request, it is first updated via the Google Wallet API. Then, if the corresponding PKPass file that was created when the Google Wallet pass was created has been registered on an iOS device, a push token is sent to the iOS device signaling an update is available. The pass converter implements the web service endpoints required for managing updates to PKPass files. Consult the Apple documentation for further information.
Note: Managing updates to PKPass files requires the use of both Apple Push Notifications, and an internal database. Each of these are configured via config.json
(see configuration). Consult the node-apn and typeorm documentation for configuration details.
On both platforms, most errors occur due to missing fields. You can identify them by checking the below.
The Google Wallet APIs are used to create passes. When errors occur, the API response is output to the local terminal including a detailed error message.
If a PKPass file cannot be opened, follow the below steps.
- Open the Console app on OSX
- Start a new session and filter by "pkpass"
- Try to open the pass on the same device
A detailed error message will appear in the Console app.