This document describes the system architecture and component design of the Wallet Service.
┌─────────────────────────────────────────────────────────────────────────┐
│ Wallet Service │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────────┐ │
│ │ Fastify │ │ Controllers │ │ Services │ │
│ │ Server │ ─▶│ │──▶│ │ │
│ │ │ │ - Apple │ │ - DigitalMembershipService │ │
│ │ - Routing │ │ - Google │ │ - ApplePasskitService │ │
│ │ - Plugins │ │ - Passes │ │ - GoogleWalletService │ │
│ └──────────────┘ └──────────────┘ └──────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ Modules │ │ External APIs │ │
│ │ │ │ │ │
│ │ - Apple │ │ - Salesforce │ │
│ │ - Google │ │ - SMTP │ │
│ │ - Salesforce │ │ - APNs │ │
│ └──────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
src/app/)app.ts - Application CoreThe main Fastify application instance implementing:
App.getInstance()Key Features:
app.environment.ts - ConfigurationCentralized environment configuration using envalid for:
src/lib/controllers/)Controllers handle HTTP requests and route them to appropriate services.
passes.controller.tsGeneral pass management operations:
GET /passes/update/:serialNumber - Trigger pass updateGET /passes/send - Send passes via emailapple.controller.tsApple PassKit WebService implementation:
GET /passes/:passTypeIdentifier/:serialNumber - Download passPOST /devices/.../registrations/... - Register deviceDELETE /devices/.../registrations/... - Unregister deviceGET /devices/.../registrations/:passTypeIdentifier - List passesPOST /log - Receive Apple logsgoogle.controller.tsGoogle Wallet callback handling:
POST /google/notify - Process Google notificationssrc/lib/services/)Services contain business logic and orchestrate operations.
DigitalMembershipServiceCore membership pass operations:
updateApplePassBySerialNumber() - Update Apple passupdateGooglePassBySerialNumber() - Update Google passretrieveApplePassesBySalesforceId() - Get Apple passes for memberretrieveGooglePassesBySalesforceId() - Get Google passes for memberApplePasskitServiceApple Wallet-specific operations:
GoogleWalletServiceGoogle Wallet-specific operations:
src/lib/modules/)Reusable utility modules for external integrations.
apple.tsApple PassKit utilities:
.pkpass files)google.tsGoogle Wallet utilities:
salesforce.tsSalesforce integration:
1. Request → PassesController.send()
2. DigitalMembershipService.retrieveApplePassesBySalesforceId()
3. Salesforce module → Fetch member data
4. Apple module → Generate .pkpass file
5. Email → Send to member
1. Data changes in Salesforce
2. Trigger → GET /passes/update/:serialNumber
3. DigitalMembershipService.updateApplePassBySerialNumber()
4. APNs → Push notification to registered devices
5. Device → GET /apple/v1/passes/:passTypeIdentifier/:serialNumber
6. Return updated pass
1. Request → PassesController.send()
2. DigitalMembershipService.retrieveGooglePassesBySalesforceId()
3. Salesforce module → Fetch member data
4. Google module → Generate JWT save link
5. Email → Send save link to member
The service uses @fastify-decorators/simple-di for dependency injection:
// Service definition
@Service()
export class DigitalMembershipService {
@Initializer()
async init() { /* ... */ }
}
// Controller usage
@Controller('/passes')
class PassesController {
private readonly _service = getInstanceByToken<DigitalMembershipService>(DigitalMembershipService);
}
┌──────────────┐ ┌─────────────────┐ ┌────────────────┐
│ Apple │ │ Authorization │ │ Controller │
│ Device │────▶│ Middleware │────▶│ Handler │
└──────────────┘ │ │ └────────────────┘
│ verifyToken() │
│ - Format check │
│ - Token match │
└─────────────────┘
┌──────────────┐ ┌─────────────────────┐ ┌────────────────┐
│ Google │ │ PaymentMethod │ │ Controller │
│ Wallet │────▶│ TokenRecipient │────▶│ Handler │
└──────────────┘ │ │ └────────────────┘
│ - Fetch public keys│
│ - Verify signature │
│ - Unseal message │
└─────────────────────┘
| Plugin | Purpose |
|---|---|
@fastify/cors |
Cross-Origin Resource Sharing |
@fastify/helmet |
Security headers |
@fastify/rate-limit |
Request rate limiting |
@fastify/circuit-breaker |
Failure protection |
@fastify/compress |
Response compression |
@fastify/swagger |
API documentation |
@fastify/static |
Static file serving |
@fastify/view |
Template rendering |
@mgcrea/fastify-graceful-exit |
Graceful shutdown |
@sentry/node |
Error tracking |
update-google-passes.tsScheduled job for batch updating Google Wallet passes.
Uses @fastify/schedule for cron-based execution.
The application implements centralized error handling:
// 404 Handler
this._instance.setNotFoundHandler(async (request) => {
throw new Error('404');
});
// Global Error Handler
this._instance.setErrorHandler(async (error, request, reply) => {
// Log to Sentry
this._sentry.captureException(error);
// Return standardized response
await reply.status(statusCode).send(buildResponseObject(statusCode));
});
# docker-compose.yml
services:
wallet-service:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
volumes:
- ./certs:/app/src/lib/certs
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Load │ │ Wallet │ │ External │
│ Balancer │────▶│ Service │────▶│ Services │
│ │ │ (Container) │ │ │
│ - TLS │ │ │ │ - Salesforce │
│ - Rate Limit │ │ - Port 3000 │ │ - SMTP │
└───────────────┘ └───────────────┘ │ - APNs │
│ - Google API │
└───────────────┘