sbcwallet 0.0.1 β 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -140
- package/dist/adapters/apple.js +17 -10
- package/dist/adapters/google.d.ts +5 -0
- package/dist/adapters/google.js +49 -24
- package/dist/api/unified.d.ts +12 -1
- package/dist/api/unified.js +63 -9
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/types.d.ts +57 -0
- package/dist/types.js +17 -1
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.js +24 -0
- package/package.json +26 -7
- package/.env.example +0 -10
- package/.github/workflows/build.yml +0 -66
- package/.github/workflows/release.yml +0 -57
- package/APPLE_WALLET_SETUP.md +0 -318
- package/GOOGLE_WALLET_SETUP.md +0 -473
- package/examples/.loyalty-fixed-state.json +0 -10
- package/examples/claim-flow.ts +0 -163
- package/examples/loyalty-admin-server.js +0 -207
- package/examples/loyalty-admin.html +0 -260
- package/examples/loyalty-fixed-card-server.js +0 -288
- package/examples/loyalty-flow.ts +0 -78
- package/examples/loyalty-google-issue.js +0 -115
- package/scripts/copy-assets.js +0 -35
- package/scripts/smoke-dist-import.js +0 -39
- package/setup-google-class.js +0 -97
- package/setup-google-class.ts +0 -105
- package/src/adapters/apple.ts +0 -193
- package/src/adapters/google.ts +0 -521
- package/src/api/unified.ts +0 -487
- package/src/index.ts +0 -74
- package/src/profiles/healthcare/index.ts +0 -157
- package/src/profiles/logistics/index.ts +0 -158
- package/src/profiles/loyalty/index.ts +0 -87
- package/src/templates/apple/child.json +0 -59
- package/src/templates/apple/parent.json +0 -54
- package/src/templates/google/child_object.json +0 -38
- package/src/templates/google/loyalty_class.json +0 -7
- package/src/templates/google/loyalty_object.json +0 -29
- package/src/templates/google/parent_class.json +0 -10
- package/src/templates/google/parent_object.json +0 -33
- package/src/types.ts +0 -324
- package/src/utils/progress-image.ts +0 -130
- package/test-google-wallet.js +0 -78
- package/test-google-wallet.ts +0 -94
- package/tests/adapters.test.ts +0 -244
- package/tests/loyalty.test.ts +0 -39
- package/tests/unified.test.ts +0 -388
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -12
package/README.md
CHANGED
|
@@ -1,187 +1,148 @@
|
|
|
1
|
-
#
|
|
1
|
+
# sbcwallet
|
|
2
2
|
|
|
3
|
-
Unified
|
|
3
|
+
Unified wallet-pass SDK for Apple Wallet (.pkpass) and Google Wallet.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
Built on @sbcwallet, it bridges cryptographic truth and real-world credentials β enabling secure, interoperable workflows for logistics, healthcare, and beyond.
|
|
5
|
+
## Install
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
sbcwallet provides a unified abstraction layer for issuing and updating wallet passes across multiple ecosystems.
|
|
13
|
-
It standardizes claim flows (like PES β TO) and status pipelines (ISSUED β PRESENCE β OPS β EXITED) while maintaining verifiable hashes, signatures, and anchor integrity via sbcwallet Core.
|
|
14
|
-
|
|
15
|
-
βΈ»
|
|
7
|
+
```sh
|
|
8
|
+
npm install sbcwallet
|
|
9
|
+
```
|
|
16
10
|
|
|
17
|
-
##
|
|
11
|
+
## Quickstart (Loyalty)
|
|
18
12
|
|
|
19
|
-
|
|
13
|
+
Multi-tenant loyalty is designed for a real-world setup:
|
|
14
|
+
- Each business (tenant) defines its own card design (logo, colors, issuer name).
|
|
15
|
+
- Users add a card using their own `memberId`.
|
|
16
|
+
- Points can be updated for an existing issued card.
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
### Define a business (per-tenant theme) and create its program
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
```ts
|
|
21
|
+
import { createBusiness, createLoyaltyProgram } from 'sbcwallet'
|
|
22
|
+
|
|
23
|
+
const biz = createBusiness({
|
|
24
|
+
name: 'X Cafe',
|
|
25
|
+
programName: 'Spirit Rewards',
|
|
26
|
+
pointsLabel: 'Points',
|
|
27
|
+
wallet: {
|
|
28
|
+
googleWallet: {
|
|
29
|
+
issuerName: 'X Cafe',
|
|
30
|
+
backgroundColor: '#111827',
|
|
31
|
+
logoUrl: 'https://example.com/logo.png',
|
|
32
|
+
|
|
33
|
+
// Advanced passthrough: merged into the Google loyaltyClass payload
|
|
34
|
+
classOverrides: {
|
|
35
|
+
reviewStatus: 'UNDER_REVIEW'
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
appleWallet: {
|
|
39
|
+
organizationName: 'X Cafe',
|
|
40
|
+
logoText: 'X',
|
|
41
|
+
backgroundColor: 'rgb(17, 24, 39)',
|
|
42
|
+
|
|
43
|
+
// Advanced passthrough: merged into the Apple pass.json payload
|
|
44
|
+
passOverrides: {
|
|
45
|
+
userInfo: { tenant: 'spirit-hub' }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
28
49
|
})
|
|
29
50
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
await createLoyaltyProgram({
|
|
52
|
+
businessId: biz.id,
|
|
53
|
+
locations: [
|
|
54
|
+
{ latitude: 35.6892, longitude: 51.389 },
|
|
55
|
+
{ latitude: 35.7, longitude: 51.4 }
|
|
56
|
+
],
|
|
57
|
+
// Apple Wallet: shown when the pass becomes relevant (e.g., near a location)
|
|
58
|
+
relevantText: 'Welcome back β show this card at checkout',
|
|
59
|
+
countryCode: 'OM',
|
|
60
|
+
homepageUrl: 'https://example.com'
|
|
35
61
|
})
|
|
36
|
-
|
|
37
|
-
// 3οΈβ£ Generate Apple Wallet pass
|
|
38
|
-
const buf = await getPkpassBuffer('child', to)
|
|
39
|
-
await fs.promises.writeFile('ticket.pkpass', buf)
|
|
40
62
|
```
|
|
41
63
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Each business defines its own loyalty program, customers create accounts, and each customer gets a loyalty card that shows:
|
|
45
|
-
- A QR/barcode identifier (`memberId`)
|
|
46
|
-
- Current points (`points`) which can be updated
|
|
64
|
+
### Issue a card and generate a Save URL
|
|
47
65
|
|
|
48
66
|
```ts
|
|
49
67
|
import {
|
|
50
|
-
createBusiness,
|
|
51
68
|
createCustomerAccount,
|
|
52
|
-
createLoyaltyProgram,
|
|
53
69
|
issueLoyaltyCard,
|
|
54
70
|
updateLoyaltyPoints,
|
|
55
71
|
getGoogleObject
|
|
56
72
|
} from 'sbcwallet'
|
|
57
73
|
|
|
58
|
-
const
|
|
59
|
-
|
|
74
|
+
const memberId = 'USER-123'
|
|
75
|
+
|
|
76
|
+
const customer = createCustomerAccount({
|
|
77
|
+
businessId: biz.id,
|
|
78
|
+
fullName: 'Alice',
|
|
79
|
+
memberId
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const card = await issueLoyaltyCard({
|
|
83
|
+
businessId: biz.id,
|
|
84
|
+
customerId: customer.id,
|
|
85
|
+
initialPoints: 10,
|
|
86
|
+
metadata: {
|
|
87
|
+
googleWallet: {
|
|
88
|
+
objectOverrides: {
|
|
89
|
+
linksModuleData: {
|
|
90
|
+
uris: [{ uri: 'https://example.com', description: 'Website' }]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
})
|
|
60
96
|
|
|
61
|
-
const customer = createCustomerAccount({ businessId: biz.id, fullName: 'Alice' })
|
|
62
|
-
const card = await issueLoyaltyCard({ businessId: biz.id, customerId: customer.id, initialPoints: 10 })
|
|
63
97
|
await updateLoyaltyPoints({ cardId: card.id, delta: 5 })
|
|
64
98
|
|
|
65
99
|
const { saveUrl } = await getGoogleObject('child', card)
|
|
66
100
|
console.log(saveUrl)
|
|
67
101
|
```
|
|
68
102
|
|
|
69
|
-
|
|
103
|
+
## Location-based surfacing and notifications
|
|
70
104
|
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
sbcwallet
|
|
74
|
-
βββ adapters/ # Apple + Google Wallet adapters
|
|
75
|
-
βββ api/ # Unified issuance/update API
|
|
76
|
-
βββ profiles/ # Domain-specific field maps
|
|
77
|
-
βββ templates/ # JSON templates for passes
|
|
78
|
-
βββ types.ts # Shared types and validation
|
|
79
|
-
```
|
|
80
|
-
### Key Components
|
|
81
|
-
```bash
|
|
82
|
-
Module Description
|
|
83
|
-
adapters/apple.ts Builds and signs .pkpass files using passkit-generator.
|
|
84
|
-
adapters/google.ts Creates Google Wallet class/object JSON payloads.
|
|
85
|
-
api/unified.ts Unified functions: createParentSchedule, createChildTicket, updatePassStatus.
|
|
86
|
-
profiles/ Domain-specific mappings (logistics, healthcare, etc.).
|
|
87
|
-
templates/ JSON templates for field mapping and layout.
|
|
88
|
-
```
|
|
105
|
+
This SDK supports two related concepts:
|
|
89
106
|
|
|
90
|
-
|
|
107
|
+
1) Location-based surfacing (no server required)
|
|
108
|
+
- Apple Wallet: setting `locations` and `relevantText` in pass.json can surface the pass on the lock screen when the user is near the business.
|
|
109
|
+
- Google Wallet: setting `locations` on the class/object helps Wallet surface the pass contextually.
|
|
91
110
|
|
|
92
|
-
|
|
111
|
+
2) Push-style notifications (server required)
|
|
112
|
+
- Google Wallet supports sending a message via the `addMessage` API. Your system decides *when* to send the message (for example, after your app detects the user is near the business).
|
|
93
113
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
Entity Description Example
|
|
97
|
-
Parent (PES) Program Entry Schedule Gate window, site, available slots
|
|
98
|
-
Child (TO) Transport Order Plate, carrier, client, status
|
|
99
|
-
Statuses ISSUED β PRESENCE β SCALE β OPS β EXITED
|
|
100
|
-
|
|
101
|
-
### Healthcare (reference)
|
|
102
|
-
|
|
103
|
-
Entity Description Example
|
|
104
|
-
Parent Appointment Batch Doctor, location, date
|
|
105
|
-
Child Patient Visit Ticket Patient, procedure, status
|
|
106
|
-
Statuses SCHEDULED β CHECKIN β PROCEDURE β DISCHARGED
|
|
114
|
+
```ts
|
|
115
|
+
import { pushLoyaltyMessage } from 'sbcwallet'
|
|
107
116
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
117
|
+
await pushLoyaltyMessage({
|
|
118
|
+
cardId: card.id,
|
|
119
|
+
header: 'X',
|
|
120
|
+
body: 'You are nearby β show this card to earn points.',
|
|
121
|
+
messageType: 'TEXT_AND_NOTIFY'
|
|
122
|
+
})
|
|
111
123
|
```
|
|
112
124
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
## π Integration with sbcwallet Core
|
|
116
|
-
|
|
117
|
-
sbcwallet Pass automatically uses:
|
|
118
|
-
β’ hashEvent() for deterministic hashes
|
|
119
|
-
β’ signCredential() for ECDSA signatures
|
|
120
|
-
β’ dailyMerkle() for anchoring batches
|
|
121
|
-
|
|
122
|
-
This ensures every pass is cryptographically verifiable and compatible with sbcwalletβs event audit trail.
|
|
123
|
-
|
|
124
|
-
βΈ»
|
|
125
|
-
|
|
126
|
-
## π§ͺ Testing
|
|
127
|
-
|
|
128
|
-
`npm run test`
|
|
129
|
-
|
|
130
|
-
Tests include:
|
|
131
|
-
β’ Apple .pkpass field mapping
|
|
132
|
-
β’ Google Wallet JSON validity
|
|
133
|
-
β’ Cross-profile field validation
|
|
134
|
-
β’ Core integration (hash + sign + verify)
|
|
135
|
-
|
|
136
|
-
βΈ»
|
|
125
|
+
## Demo server (multi-tenant)
|
|
137
126
|
|
|
138
|
-
## βοΈ Environment Variables (Apple Wallet)
|
|
139
|
-
|
|
140
|
-
```sh
|
|
141
|
-
APPLE_TEAM_ID=ABCD1234
|
|
142
|
-
APPLE_PASS_TYPE_ID=pass.com.sbcwallet.logistics
|
|
143
|
-
APPLE_CERT_PATH=./certs/pass.p12
|
|
144
|
-
APPLE_CERT_PASSWORD=yourpassword
|
|
145
|
-
APPLE_WWDR_PATH=./certs/wwdr.pem
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
For Google Wallet, include:
|
|
149
127
|
```sh
|
|
150
|
-
|
|
151
|
-
GOOGLE_SA_JSON=./google/credentials.json
|
|
128
|
+
npm run loyalty:server:multi
|
|
152
129
|
```
|
|
153
130
|
|
|
154
|
-
|
|
131
|
+
Open `http://localhost:5190`.
|
|
155
132
|
|
|
156
|
-
##
|
|
133
|
+
## Configuration
|
|
157
134
|
|
|
158
|
-
|
|
159
|
-
|
|
135
|
+
For Google Wallet Save URLs to work on-device you must set:
|
|
136
|
+
- `GOOGLE_ISSUER_ID`
|
|
137
|
+
- `GOOGLE_SA_JSON`
|
|
160
138
|
|
|
161
|
-
|
|
139
|
+
For Apple Wallet signing, see APPLE_WALLET_SETUP.md.
|
|
162
140
|
|
|
163
|
-
##
|
|
164
|
-
1. Fork the repo
|
|
165
|
-
2. Run npm install
|
|
166
|
-
3. Add or improve a profile under src/profiles/
|
|
167
|
-
4. Write tests in tests/
|
|
168
|
-
5. Submit a PR using conventional commits
|
|
141
|
+
## Development
|
|
169
142
|
|
|
170
|
-
βΈ»
|
|
171
|
-
|
|
172
|
-
## π§ Part of the sbcwallet Ecosystem
|
|
173
|
-
|
|
174
|
-
Repo Purpose
|
|
175
143
|
```sh
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
sbcwallet/id Hosted identity & orchestration layer (SaaS)
|
|
144
|
+
npm run build
|
|
145
|
+
npm test
|
|
146
|
+
npm pack --dry-run
|
|
180
147
|
```
|
|
181
148
|
|
|
182
|
-
βΈ»
|
|
183
|
-
|
|
184
|
-
βsbcwallet Pass connects cryptographic truth with human experience β
|
|
185
|
-
turning every credential into a verifiable story.β
|
|
186
|
-
|
|
187
|
-
Reflection: evidence β logic consistent brevity optimized
|
package/dist/adapters/apple.js
CHANGED
|
@@ -27,26 +27,28 @@ export class AppleWalletAdapter {
|
|
|
27
27
|
const template = this.mergeTemplates(baseTemplate, profileTemplate);
|
|
28
28
|
// Apply pass data to template
|
|
29
29
|
const populatedTemplate = this.populateTemplate(template, passData, profile, passType);
|
|
30
|
+
// Optional per-pass overrides via metadata (useful for per-business theming)
|
|
31
|
+
const appleWallet = passData?.metadata?.appleWallet || {};
|
|
30
32
|
// Build pass props from populated template
|
|
31
33
|
const passProps = {
|
|
32
34
|
serialNumber: passData.id,
|
|
33
|
-
description: populatedTemplate.description || 'sbcwallet Pass',
|
|
34
|
-
organizationName: populatedTemplate.organizationName || 'sbcwallet',
|
|
35
|
+
description: appleWallet.description || populatedTemplate.description || 'sbcwallet Pass',
|
|
36
|
+
organizationName: appleWallet.organizationName || populatedTemplate.organizationName || 'sbcwallet',
|
|
35
37
|
passTypeIdentifier: this.config.passTypeId,
|
|
36
38
|
teamIdentifier: this.config.teamId
|
|
37
39
|
};
|
|
38
40
|
// Add colors
|
|
39
|
-
if (populatedTemplate.backgroundColor) {
|
|
40
|
-
passProps.backgroundColor = populatedTemplate.backgroundColor;
|
|
41
|
+
if (appleWallet.backgroundColor || populatedTemplate.backgroundColor) {
|
|
42
|
+
passProps.backgroundColor = appleWallet.backgroundColor || populatedTemplate.backgroundColor;
|
|
41
43
|
}
|
|
42
|
-
if (populatedTemplate.foregroundColor) {
|
|
43
|
-
passProps.foregroundColor = populatedTemplate.foregroundColor;
|
|
44
|
+
if (appleWallet.foregroundColor || populatedTemplate.foregroundColor) {
|
|
45
|
+
passProps.foregroundColor = appleWallet.foregroundColor || populatedTemplate.foregroundColor;
|
|
44
46
|
}
|
|
45
|
-
if (populatedTemplate.labelColor) {
|
|
46
|
-
passProps.labelColor = populatedTemplate.labelColor;
|
|
47
|
+
if (appleWallet.labelColor || populatedTemplate.labelColor) {
|
|
48
|
+
passProps.labelColor = appleWallet.labelColor || populatedTemplate.labelColor;
|
|
47
49
|
}
|
|
48
|
-
if (populatedTemplate.logoText) {
|
|
49
|
-
passProps.logoText = populatedTemplate.logoText;
|
|
50
|
+
if (appleWallet.logoText || populatedTemplate.logoText) {
|
|
51
|
+
passProps.logoText = appleWallet.logoText || populatedTemplate.logoText;
|
|
50
52
|
}
|
|
51
53
|
// Add barcodes
|
|
52
54
|
if (populatedTemplate.barcodes && populatedTemplate.barcodes.length > 0) {
|
|
@@ -56,6 +58,11 @@ export class AppleWalletAdapter {
|
|
|
56
58
|
if (populatedTemplate.generic) {
|
|
57
59
|
passProps.generic = populatedTemplate.generic;
|
|
58
60
|
}
|
|
61
|
+
// Advanced passthrough: allow issuers to supply any PassKit fields.
|
|
62
|
+
// Example: webServiceURL, authenticationToken, appLaunchURL, userInfo, beacons, nfc, etc.
|
|
63
|
+
if (appleWallet.passOverrides && typeof appleWallet.passOverrides === 'object') {
|
|
64
|
+
Object.assign(passProps, appleWallet.passOverrides);
|
|
65
|
+
}
|
|
59
66
|
// Create pass
|
|
60
67
|
const pass = new PKPass({}, {
|
|
61
68
|
wwdr: this.config.wwdrPath,
|
|
@@ -13,6 +13,11 @@ export declare class GoogleWalletAdapter {
|
|
|
13
13
|
private getFieldValue;
|
|
14
14
|
private generateSaveUrl;
|
|
15
15
|
private upsertInAPI;
|
|
16
|
+
addMessageToLoyaltyObject(loyaltyObjectId: string, message: {
|
|
17
|
+
header: string;
|
|
18
|
+
body: string;
|
|
19
|
+
messageType?: string;
|
|
20
|
+
}): Promise<void>;
|
|
16
21
|
/**
|
|
17
22
|
* Generate and add hero image with progress bar to the pass object
|
|
18
23
|
*
|
package/dist/adapters/google.js
CHANGED
|
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'url';
|
|
|
3
3
|
import { dirname, join } from 'path';
|
|
4
4
|
import { GoogleAuth } from 'google-auth-library';
|
|
5
5
|
import { generateLogisticsHeroImage, generateHealthcareHeroImage } from '../utils/progress-image.js';
|
|
6
|
+
import { logDebug, logWarn, logError } from '../utils/logger.js';
|
|
6
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
8
|
const __dirname = dirname(__filename);
|
|
8
9
|
export class GoogleWalletAdapter {
|
|
@@ -32,14 +33,14 @@ export class GoogleWalletAdapter {
|
|
|
32
33
|
await this.upsertInAPI('loyaltyClass', classPayload);
|
|
33
34
|
}
|
|
34
35
|
catch (error) {
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
logWarn('Google Wallet API loyaltyClass create/update failed');
|
|
37
|
+
logWarn(error);
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
else {
|
|
40
|
-
|
|
41
|
+
logWarn('No Google Auth - class not created in API (will not work on device)');
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
+
logDebug('Google Wallet Class:', JSON.stringify(classPayload, null, 2));
|
|
43
44
|
// Save URL is not applicable for classes.
|
|
44
45
|
return { object: classPayload, saveUrl: '' };
|
|
45
46
|
}
|
|
@@ -85,20 +86,20 @@ export class GoogleWalletAdapter {
|
|
|
85
86
|
}
|
|
86
87
|
catch (error) {
|
|
87
88
|
// Keep going so we can still generate a signed Save URL for debugging/testing.
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
logWarn('Google Wallet API object create/update failed; continuing to Save URL generation');
|
|
90
|
+
logWarn(error);
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
else {
|
|
93
|
-
|
|
94
|
+
logWarn('No Google Auth - object not created in API (will not work on device)');
|
|
94
95
|
}
|
|
95
96
|
// Generate save URL with signed JWT
|
|
96
97
|
// Prefer embedding the full object in the JWT payload so the Save URL can work
|
|
97
98
|
// even if the object was not pre-created in the API.
|
|
98
99
|
const saveUrl = await this.generateSaveUrl(populatedObject, isLoyalty ? 'loyaltyObjects' : 'genericObjects');
|
|
99
100
|
// Log the object
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
logDebug('Google Wallet Object:', JSON.stringify(populatedObject, null, 2));
|
|
102
|
+
logDebug('Save URL:', saveUrl);
|
|
102
103
|
return {
|
|
103
104
|
object: populatedObject,
|
|
104
105
|
saveUrl
|
|
@@ -120,10 +121,16 @@ export class GoogleWalletAdapter {
|
|
|
120
121
|
...baseTemplate,
|
|
121
122
|
...profileTemplate,
|
|
122
123
|
id: classId,
|
|
123
|
-
|
|
124
|
+
// Prefer business/program-provided metadata over template defaults.
|
|
125
|
+
issuerName: googleWallet.issuerName || profileTemplate.issuerName || baseTemplate.issuerName || 'sbcwallet',
|
|
124
126
|
programName: passData.programName || googleWallet.programName || 'Loyalty',
|
|
125
127
|
hexBackgroundColor: googleWallet.backgroundColor || baseTemplate.hexBackgroundColor || '#111827'
|
|
126
128
|
};
|
|
129
|
+
// Geo locations (latitude/longitude pairs) for location-based surfacing.
|
|
130
|
+
const locations = googleWallet.locations || metadata.locations;
|
|
131
|
+
if (Array.isArray(locations) && locations.length > 0) {
|
|
132
|
+
payload.locations = locations;
|
|
133
|
+
}
|
|
127
134
|
if (googleWallet.countryCode)
|
|
128
135
|
payload.countryCode = googleWallet.countryCode;
|
|
129
136
|
if (googleWallet.homepageUrl) {
|
|
@@ -280,7 +287,7 @@ export class GoogleWalletAdapter {
|
|
|
280
287
|
const baseUrl = 'https://pay.google.com/gp/v/save';
|
|
281
288
|
const objectId = passObject.id;
|
|
282
289
|
if (!this.config.serviceAccountPath) {
|
|
283
|
-
|
|
290
|
+
logWarn('No service account - returning unsigned URL (will not work)');
|
|
284
291
|
return `${baseUrl}/${encodeURIComponent(objectId)}`;
|
|
285
292
|
}
|
|
286
293
|
try {
|
|
@@ -306,7 +313,7 @@ export class GoogleWalletAdapter {
|
|
|
306
313
|
return `${baseUrl}/${token}`;
|
|
307
314
|
}
|
|
308
315
|
catch (error) {
|
|
309
|
-
|
|
316
|
+
logError('Error generating signed JWT:', error);
|
|
310
317
|
return `${baseUrl}/${encodeURIComponent(objectId)}`;
|
|
311
318
|
}
|
|
312
319
|
}
|
|
@@ -324,12 +331,12 @@ export class GoogleWalletAdapter {
|
|
|
324
331
|
method: 'POST',
|
|
325
332
|
data: payload
|
|
326
333
|
});
|
|
327
|
-
|
|
334
|
+
logDebug(`β
${kind} created in Google Wallet API`);
|
|
328
335
|
}
|
|
329
336
|
catch (error) {
|
|
330
337
|
if (error.response?.status === 409) {
|
|
331
338
|
// Object already exists, try to update it
|
|
332
|
-
|
|
339
|
+
logDebug('βΉοΈ Resource exists, updating...');
|
|
333
340
|
try {
|
|
334
341
|
const client = await this.auth.getClient();
|
|
335
342
|
const baseUrl = 'https://walletobjects.googleapis.com/walletobjects/v1';
|
|
@@ -340,19 +347,37 @@ export class GoogleWalletAdapter {
|
|
|
340
347
|
method: 'PUT',
|
|
341
348
|
data: payload
|
|
342
349
|
});
|
|
343
|
-
|
|
350
|
+
logDebug(`β
${kind} updated in Google Wallet API`);
|
|
344
351
|
}
|
|
345
352
|
catch (updateError) {
|
|
346
|
-
|
|
353
|
+
logError(`β Error updating ${kind}:`, updateError);
|
|
347
354
|
throw updateError;
|
|
348
355
|
}
|
|
349
356
|
}
|
|
350
357
|
else {
|
|
351
|
-
|
|
358
|
+
logError(`β Error creating ${kind}:`, error.response?.data || error.message);
|
|
352
359
|
throw error;
|
|
353
360
|
}
|
|
354
361
|
}
|
|
355
362
|
}
|
|
363
|
+
async addMessageToLoyaltyObject(loyaltyObjectId, message) {
|
|
364
|
+
if (!this.auth) {
|
|
365
|
+
throw new Error('Google Auth not initialized');
|
|
366
|
+
}
|
|
367
|
+
const client = await this.auth.getClient();
|
|
368
|
+
const baseUrl = 'https://walletobjects.googleapis.com/walletobjects/v1';
|
|
369
|
+
await client.request({
|
|
370
|
+
url: `${baseUrl}/loyaltyObject/${encodeURIComponent(loyaltyObjectId)}/addMessage`,
|
|
371
|
+
method: 'POST',
|
|
372
|
+
data: {
|
|
373
|
+
message: {
|
|
374
|
+
header: message.header,
|
|
375
|
+
body: message.body,
|
|
376
|
+
messageType: message.messageType || 'TEXT_AND_NOTIFY'
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
356
381
|
/**
|
|
357
382
|
* Generate and add hero image with progress bar to the pass object
|
|
358
383
|
*
|
|
@@ -381,11 +406,11 @@ export class GoogleWalletAdapter {
|
|
|
381
406
|
// Ensure directory exists
|
|
382
407
|
try {
|
|
383
408
|
await writeFile(imagePath, imageBuffer);
|
|
384
|
-
|
|
409
|
+
logDebug(`β¨ Hero image saved: ${imagePath}`);
|
|
385
410
|
}
|
|
386
411
|
catch (err) {
|
|
387
412
|
// Directory might not exist, that's OK - just skip for now
|
|
388
|
-
|
|
413
|
+
logDebug('βΉοΈ Hero image generated (not uploaded - requires public URL)');
|
|
389
414
|
}
|
|
390
415
|
// TODO: Upload to cloud storage and get public URL
|
|
391
416
|
// For now, we'll skip adding the hero image to the pass object
|
|
@@ -410,10 +435,10 @@ export class GoogleWalletAdapter {
|
|
|
410
435
|
DISCHARGED: '#7ED321'
|
|
411
436
|
};
|
|
412
437
|
passObject.hexBackgroundColor = statusColors[passData.status] || '#4A90E2';
|
|
413
|
-
|
|
438
|
+
logDebug(`β¨ Dynamic color applied for status: ${passData.status} (${passObject.hexBackgroundColor})`);
|
|
414
439
|
}
|
|
415
440
|
catch (error) {
|
|
416
|
-
|
|
441
|
+
logError('β οΈ Failed to generate hero image:', error);
|
|
417
442
|
// Continue without hero image if generation fails
|
|
418
443
|
}
|
|
419
444
|
}
|
|
@@ -421,9 +446,9 @@ export class GoogleWalletAdapter {
|
|
|
421
446
|
// Stub for creating a Google Wallet class
|
|
422
447
|
// In a real implementation, this would call the Google Wallet API
|
|
423
448
|
const classId = `${this.config.issuerId}.${profile.name}_${passType}`;
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
449
|
+
logDebug(`Creating Google Wallet Class: ${classId}`);
|
|
450
|
+
logDebug('Profile:', profile.name);
|
|
451
|
+
logDebug('Type:', passType);
|
|
427
452
|
// This would normally make an API call to:
|
|
428
453
|
// POST https://walletobjects.googleapis.com/walletobjects/v1/genericClass
|
|
429
454
|
}
|
package/dist/api/unified.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CreateParentInput, CreateChildInput, CreateBusinessInput, CreateCustomerAccountInput, CreateLoyaltyProgramInput, IssueLoyaltyCardInput, UpdateLoyaltyPointsInput, LoyaltyBusiness, LoyaltyCustomerAccount, ParentPassData, ChildPassData, PassData, PassStatus, ProfileType, ProfileConfig, PassGenerationResult } from '../types.js';
|
|
1
|
+
import type { CreateParentInput, CreateChildInput, CreateBusinessInput, CreateCustomerAccountInput, CreateLoyaltyProgramInput, IssueLoyaltyCardInput, UpdateLoyaltyPointsInput, PushLoyaltyMessageInput, LoyaltyBusiness, LoyaltyCustomerAccount, ParentPassData, ChildPassData, PassData, PassStatus, ProfileType, ProfileConfig, PassGenerationResult } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Get a profile by name
|
|
4
4
|
*/
|
|
@@ -31,6 +31,17 @@ export declare function issueLoyaltyCard(input: IssueLoyaltyCardInput): Promise<
|
|
|
31
31
|
* Update points on a loyalty card.
|
|
32
32
|
*/
|
|
33
33
|
export declare function updateLoyaltyPoints(input: UpdateLoyaltyPointsInput): Promise<PassData>;
|
|
34
|
+
/**
|
|
35
|
+
* Send a message to a Google Wallet loyalty object.
|
|
36
|
+
*
|
|
37
|
+
* Notes:
|
|
38
|
+
* - This uses the Google Wallet API addMessage endpoint (requires service account credentials).
|
|
39
|
+
* - Location-based surfacing is controlled by the pass locations; your system decides WHEN to send messages.
|
|
40
|
+
*/
|
|
41
|
+
export declare function pushLoyaltyMessage(input: PushLoyaltyMessageInput): Promise<{
|
|
42
|
+
ok: true;
|
|
43
|
+
objectId: string;
|
|
44
|
+
}>;
|
|
34
45
|
/**
|
|
35
46
|
* Create a parent schedule (PES or AppointmentBatch)
|
|
36
47
|
*/
|