Wallet Pass
This is the self-host app to manage Apple and Google wallet pass
Features
- Create and Update Apple and Google wallet pass
- Currently storeCard for Apple and generic pass for Google is supported
- Dynamic barcode URL proxy without need to update the pass content
- Push notification to user device
Dependencies
- Postgres DB
- S3 storage
- Google developer account
- Apple developer account
Configuration
DB provision
The project use drizzle-orm to manage DB migration, to provision DB
- Create a Postgres database and set the following env vars accordingly
- DB_HOST
- DB_PORT
- DB_USER
- DB_PASSWORD
- DB_DATABASE
- Run the migration script
> npm run migrate
Project configuration
Create a Apple pass and certificate
Follow this instruction
Create a Google pass and key
Follow this instruction
Add a new project configuration
- create new project,
POST /projects
with a name
{
"project": "your-project"
}
remember the api key returned in the response
- update project configuration,
PUT /projects
withx-stl-key
set to the api key from last step, request body
{
"config": {
"walletPass": {
"webhookUrl": "string", // the API endpoint to receive subscribed events
"subscribedEvents": [
"string" // pass:register, pass:unregister, when a pass is installed/removed on a device
],
"google": {
"issuerId": "string", // from your Google wallet pass setup
"passTypeIdentifier": "string", // from your Google wallet pass setup
"passType": "string", // "generic" is supported as of now
"credentials": "string" // json content of secret key from Google, the private key will be encrypted when storing in DB
},
"apple": {
"teamIdentifier": "string", // from your Apple wallet pass setup
"passTypeIdentifier": "string", // from your Apple wallet pass setup
"passType": "string", // "storeCard" is supported as of now
"cert": "string", // certificate content
"key": "string", // certificate private key content, the key will be encrypted when storing in DB
"keySecret": "string" // key secret if available
}
}
}
Pass template file
Upload your pass base template file to s3 bucket according to your design, example can be found here
- Google: https://resources.smarttokenlabs.com/wallet-pass/e7e74bbd-5a14-4a43-95a6-cfdf2f8eeb16/google/template.json
- Apple: https://resources.smarttokenlabs.com/wallet-pass/e7e74bbd-5a14-4a43-95a6-cfdf2f8eeb16/apple/template.zip
The service will try to find the template under S3_BUCKET/wallet-pass/TEMPLATE_ID/template.{json|zip}
, where S3_BUCKET
is configurable via env var, and TEMPLATE_ID is provided by the client request when creating the wallet pass
How to use
Create wallet pass
POST /wallet-passes
for Apple
{
"id": "EXTERNAL_ID", // set an external id to identify a wallet pass, e.g. project-apple-1
"callbackUrl": "http://127.0.0.1:3006/webhooks/wallet-pass-created", // callback endpoint when wallet pass details once creation is successful, payload will be explained later
"params": {
"templateId": "e7e74bbd-5a14-4a43-95a6-cfdf2f8eeb16", // template ID for the template file to use, which is uploaded in previous step
"platform": "apple", // "apple" or "google"
"barcode": {
"redirect": {
"url": "XXX" // url for the barcode to show on wallet pass card
},
"altText": "XXX Pass"
},
"externalId": "EXTERNAL_ID", // set an external id to identify a wallet pass, e.g. project-apple-1
"pass": {
// this section depends on your wallet pass design, please check the vendor's offical document for details
"description": "XXX",
"backFields": [
{
"key": "note",
"value": "XXX"
},
{
"key": "website",
"label": "Link",
"attributedValue": "XXX",
"value": "XXX"
}
],
"headerFields": [
{
"label": "XXX",
"textAlignment": "PKTextAlignmentRight",
"key": "XXX",
"value": "XXX"
}
],
"primaryFields": [],
"secondaryFields": [
{
"label": "XXX",
"textAlignment": "PKTextAlignmentLeft",
"key": "XXX",
"value": "XXX"
},
{
"label": "XXX",
"textAlignment": "PKTextAlignmentLeft",
"key": "XXX",
"value": "XXX"
}
]
}
}
}
for Google
{
"id": "EXTERNAL_ID", // set an external id to identify a wallet pass, e.g. project-google-1
"callbackUrl": "http://127.0.0.1:3006/webhooks/wallet-pass-created", // callback endpoint when wallet pass details once creation is successful, payload will be explained
"params": {
"templateId": "e7e74bbd-5a14-4a43-95a6-cfdf2f8eeb16", // template ID for the template file to use, which is uploaded in previous step
"platform": "google", // "apple" or "google"
"barcode": {
"redirect": {
"url": "XXX" // url for the barcode to show on wallet pass card
},
"altText": "XXX"
},
"externalId": "EXTERNAL_ID", // set an external id to identify a wallet pass, e.g. project-apple-1
"pass": {
// this section depends on your wallet pass design, please check the vendor's offical document for details
"logo": {
"sourceUri": {
"uri": "XXX URL"
}
},
"heroImage": {
"sourceUri": {
"uri": "XXX URL"
}
},
"linksModuleData": {
"uris": [
{
"description": "XXX",
"id": "website",
"uri": "XXX URL"
}
]
},
"hexBackgroundColor": "#ffffff",
"cardTitle": {
"defaultValue": {
"language": "en",
"value": "XXX"
}
},
"subheader": {
"defaultValue": {
"language": "en",
"value": "XXX"
}
},
"header": {
"defaultValue": {
"language": "en",
"value": "XXX"
}
},
"textModulesData": [
{
"id": "XXX",
"header": "XXX",
"body": "XXX"
},
{
"id": "XXX",
"header": "XXX",
"body": "XXX"
}
]
}
}
}
for the callbackUrl
, it will be called when the wallet pass is successfully created, and the payload is like
{
"id": "EXTERNAL_ID", // the external id passed during create request
"result": {
"id": "XXX", // PRIMARY_KEY in wallet pass db for reference, uuid
"fileURL": "${env.ROOT_URL}/link/wallet-pass/${primaryKey}", // the url to install the wallet pass
"platform": "XXX", // "apple" or "google"
"createdAt": "XXX", // timestamp
"externalId": "EXTERNAL_ID" // the external id passed during create request
},
"signedMessage": "0xXXXXX"
}
the signedMessage
is created with ethers wallet crypto signature
const wallet = new ethers.Wallet(env.SIGNATURE_PRIVATE_KEY);
const messageToSign = `${task.id}-${JSON.stringify(result.result)}`;
const signedMessage = await wallet.signMessage(messageToSign);
it's used as an authentication mechanism for client to verify the callback request
Update wallet pass
PUT /wallet-passes/
for Apple
{
"id": "a409cc64-7c12-4f36-97c2-efc0d1a6e206", // PRIMARY_KEY in wallet pass db for reference, uuid
"params": {
"pass": {
// this section depends on your wallet pass design, please check the vendor's offical document for details
"backFields": [
{
"key": "note",
"value": "XXX"
},
{
"key": "website",
"label": "Link",
"attributedValue": "XXX",
"value": "XXX"
},
{
// Notification info
"key": "notification",
"label": "XXX",
"value": "XXX",
"changeMessage": "%@",
"textAlignment": "PKTextAlignmentNatural"
}
],
"primaryFields": [],
"secondaryFields": [
{
"label": "XXX",
"textAlignment": "PKTextAlignmentLeft",
"key": "XXX",
"value": "XXX"
},
{
"label": "XXX",
"textAlignment": "PKTextAlignmentLeft",
"key": "XXX",
"value": "XXX"
}
],
"headerFields": [
{
"label": "XXX",
"textAlignment": "PKTextAlignmentRight",
"key": "XXX",
"value": "XXX"
}
]
}
}
}
for Google
{
"id": "daf26924-277c-4037-b6a7-c2b3061bb279", // PRIMARY_KEY in wallet pass db for reference, uuid
"params": {
"message": {
// Notification info
"header": "XXX",
"body": "XXX",
"id": "XXX",
"message_type": "TEXT_AND_NOTIFY"
},
"pass": {
"logo": {
"sourceUri": {
"uri": "XXX URL"
}
},
"heroImage": {
"sourceUri": {
"uri": "XXX URL"
}
},
"linksModuleData": {
"uris": [
{
"description": "XXX",
"id": "website",
"uri": "XXX URL"
}
]
},
"hexBackgroundColor": "#ffffff",
"cardTitle": {
"defaultValue": {
"language": "en",
"value": "XXX"
}
},
"subheader": {
"defaultValue": {
"language": "en",
"value": "XXX"
}
},
"textModulesData": [
{
"id": "XXX",
"header": "XXX",
"body": "XXX"
},
{
"id": "XXX",
"header": "XXX",
"body": "XXX"
}
],
"header": {
"defaultValue": {
"language": "en",
"value": "XXX"
}
}
}
}
}