fix(loyalty): use private key PEM for JWT signing instead of RSASigner.key

RSASigner doesn't expose a .key attribute. Load the private key string
directly from the service account JSON file for PyJWT encoding. Also
adds fat JWT fallback for demo mode where DRAFT classes reject object
creation via REST API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 22:31:02 +01:00
parent 9a13aee8ed
commit 19d267587b

View File

@@ -472,34 +472,60 @@ class GoogleWalletService:
if not self.is_configured:
raise GoogleWalletNotConfiguredException()
# Try to create the object via API. If it fails (e.g. demo mode
# where DRAFT classes reject object creation), fall back to
# embedding the full object data in the JWT ("fat JWT").
if not card.google_object_id:
self.create_object(db, card)
try:
self.create_object(db, card)
except WalletIntegrationException:
logger.info(
"Object creation failed for card %s, using fat JWT",
card.id,
)
try:
import jwt
credentials = self._get_credentials()
signer = self._get_signer()
now = datetime.now(tz=UTC)
origins = settings.loyalty_google_wallet_origins or []
issuer_id = settings.loyalty_google_issuer_id
object_id = card.google_object_id or f"{issuer_id}.loyalty_card_{card.id}"
if card.google_object_id:
# Object exists in Google — reference by ID only
payload = {
"loyaltyObjects": [{"id": card.google_object_id}],
}
else:
# Object not created — embed full object data in JWT
object_data = self._build_object_data(card, object_id)
payload = {
"loyaltyObjects": [object_data],
}
claims = {
"iss": credentials.service_account_email,
"aud": "google",
"origins": origins,
"typ": "savetowallet",
"payload": {
"loyaltyObjects": [{"id": card.google_object_id}],
},
"payload": payload,
"iat": now,
"exp": now + timedelta(hours=1),
}
# Sign using the RSASigner's key_id and key bytes (public API)
# Load the private key directly from the service account file
# (RSASigner doesn't expose .key; PyJWT needs the PEM string)
with open(settings.loyalty_google_service_account_json) as f:
sa_data = json.load(f)
private_key = sa_data["private_key"]
token = jwt.encode(
claims,
signer.key,
private_key,
algorithm="RS256",
)
@@ -507,7 +533,7 @@ class GoogleWalletService:
db.commit()
return f"https://pay.google.com/gp/v/save/{token}"
except (AttributeError, ValueError, KeyError) as exc:
except (AttributeError, ValueError, KeyError, OSError) as exc:
logger.error(
"Failed to generate Google Wallet save URL: %s", exc
)