Phase 2 backend: Google Wallet pass + profile contact form

- cardclaws-wallet::google — inline GenericObject + RS256 `savetowallet` JWT
  (Rs256Signer via jsonwebtoken; FakeGoogleSigner test double)
- POST /v1/cards/{id}/wallet/google → { saveUrl }
- POST /v1/profile/{handle}/contact — validate, rate-limit, record
  contact_form_submission, email the owner (HTML-escaped, Resend)
- WalletConfig gains google issuer id + service account email

84 backend tests; fmt + clippy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-04 12:32:21 -05:00
co-authored by Claude Opus 4.8
parent 4770efd861
commit 8ae1d8f263
20 changed files with 545 additions and 1 deletions
@@ -64,6 +64,10 @@ pub struct WalletConfig {
pub apple_pass_type_id: String,
pub apple_team_id: String,
pub organization_name: String,
/// Google Wallet issuer id (the numeric issuer account).
pub google_issuer_id: String,
/// Service account email used as the save-JWT `iss`.
pub google_service_account_email: String,
}
/// Cloudflare R2 (S3-compatible) object storage config. Defaults are dev
@@ -112,6 +116,13 @@ impl Config {
.await,
apple_team_id: optional(src, "APPLE_TEAM_ID", "TEAMID0000").await,
organization_name: optional(src, "ORGANIZATION_NAME", "CardClaws").await,
google_issuer_id: optional(src, "GOOGLE_ISSUER_ID", "3388000000000000000").await,
google_service_account_email: optional(
src,
"GOOGLE_SA_EMAIL",
"[email protected]",
)
.await,
},
})
}