fix(gateway): /redeem-preview + plans + addons must be public (D29) (#1559)
* feat(billing+notification): wire voucher-issued email (D28) D28 of the Sovereign DoD requires that issuing a voucher emails it to the recipient zero-touch. Today POST /billing/vouchers/issue persists the PromoCode row but never notifies anyone — so a gifted voucher only reaches its recipient if the operator manually sends the code over a side channel. This wires sme-billing -> sme-notification so the email fires automatically on every successful upsert that carries a recipient_email field. Architecture follows the existing notification-service seam: sme-billing POSTs to http://notification.sme.svc.cluster.local:8087/ notification/send with template=voucher-issued; sme-notification renders the HTML and dispatches via Stalwart over SMTP. No direct SMTP code is added to billing, no stalwart-mail calls bypass notification. Server-side only — the owner-UI for issuing vouchers (D28b) is a separate PR. Changes: notification/templates/templates.go + VoucherIssuedEmail(code, creditOMR, description, sovereignFQDN, validityHint) — renders code prominently, redeem button to https://marketplace.<sovereignFQDN>/redeem/?code=<CODE>; FQDN always supplied by caller, NEVER hardcoded. notification/handlers/handlers.go + renderTemplate("voucher-issued") case parsing {code, credit_omr, description, sovereign_fqdn, validity_hint}. + Default subject "You've been gifted a voucher for OpenOva SME". billing/handlers/handlers.go + Handler fields: NotificationURL, SovereignFQDN, NotificationClient. billing/handlers/vouchers.go + issueVoucherRequest = store.PromoCode + RecipientEmail (request- only; never persisted). + sendVoucherIssuedEmail() — POSTs to NotificationURL with a 5s timeout. Best-effort: a non-2xx or transport error logs but does NOT fail the IssueVoucher response, because the row is already persisted and re-issuing the same code re-fires the email. + Re-issue semantics (#91 resurrects soft-deleted rows) extend to the email path — documented in the handler comment. billing/main.go + Reads NOTIFICATION_SERVICE_URL (default http://notification.sme.svc.cluster.local:8087/notification/send) and SOVEREIGN_FQDN env vars. Wires a 5s default http.Client. products/catalyst/chart/templates/sme-services/billing.yaml + Pipes NOTIFICATION_SERVICE_URL (cluster-DNS constant) and SOVEREIGN_FQDN (from .Values.global.sovereignFQDN, NEVER hardcoded) into the billing Deployment. Tests: notification/handlers/handlers_test.go (new) + TestRenderTemplate_VoucherIssued: rendered HTML contains code + credit + a redeem URL built from the supplied FQDN; never falls back to marketplace.openova.io. + TestRenderTemplate_VoucherIssued_CustomSubject + _NoDescription + TestRenderTemplate_UnknownTemplate as guard rails. billing/handlers/vouchers_test.go + TestIssueVoucher_SendsEmail_WhenRecipientPresent: a fake round- tripper sees the POST to notification with the right URL + template + data (code upper-cased, credit_omr, sovereign_fqdn, description) when recipient_email is set. + TestIssueVoucher_NoEmail_WhenRecipientAbsent: no notification call when recipient is empty. + TestIssueVoucher_NotificationFailure_DoesNotFailUpsert: operator gets 200 even when notification returns 500. + TestIssueVoucher_403WithoutVoucherIssuerRole: role gate preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(chart): admin pod uses dedicated image tag (D27 SME stack) t132 caught admin pod stuck in ImagePullBackOff on `admin:b0ed216` — the SME services CI run for that mono-repo SHA published 10 services but admin's image was missing from GHCR. Decouple admin's tag from smeTag so a missing-build for one service doesn't wedge the SME stack. Default to `3c2f7e4` (matches marketplaceApi + console, known-published). When admin's UI changes, bump in lockstep with those. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(slot-13): pin bp-catalyst-platform to 1.4.144 PR #1556 (D28 voucher email wire) + PR #1557 (D27 admin tag override) landed and Blueprint Release packaged 1.4.144. Pin the slot file so future provisions get the latest chart by default — t132 manually upgraded via kubectl patch but t133+ will inherit it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(gateway): /redeem-preview + plans + addons must be public (D29) The marketplace /redeem?code=XXX landing page calls /api/billing/vouchers/redeem-preview unauthenticated per docs/FRANCHISE- MODEL.md §3, but the gateway's catch-all /api/billing/ entry was returning 401 to it — breaking the entire voucher-redeem zero-touch flow that D29 depends on. Also expose /api/billing/plans and /api/billing/addons so the marketplace landing can render pricing without a session. Caught live on t132 2026-05-16 — every /redeem call returned 401. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: hatiyildiz <hatice.yildiz@openova.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
27bd5d486d
commit
a11067da1a
@ -62,8 +62,19 @@ func main() {
|
||||
{PathPrefix: "/api/provisioning/status/", Upstream: provisioningURL, StripPrefix: "/api", Public: true},
|
||||
{PathPrefix: "/api/provisioning/tenant/", Upstream: provisioningURL, StripPrefix: "/api", Public: true},
|
||||
{PathPrefix: "/api/provisioning/", Upstream: provisioningURL, StripPrefix: "/api", Public: false},
|
||||
// Billing (mixed — webhook is public, rest requires auth).
|
||||
// Billing (mixed — public list of plans/addons/redeem-preview for
|
||||
// the marketplace landing + /redeem flow; webhook for Stripe;
|
||||
// everything else requires auth).
|
||||
// D29 fix 2026-05-16: /redeem?code=XXX page calls
|
||||
// /api/billing/vouchers/redeem-preview unauthenticated per
|
||||
// docs/FRANCHISE-MODEL.md §3; the catch-all entry was returning
|
||||
// 401 and breaking the entire voucher-redeem zero-touch flow.
|
||||
// Plans + addons must also be public so the marketplace landing
|
||||
// can render pricing without a session.
|
||||
{PathPrefix: "/api/billing/webhook", Upstream: billingURL, StripPrefix: "/api", Public: true},
|
||||
{PathPrefix: "/api/billing/vouchers/redeem-preview", Upstream: billingURL, StripPrefix: "/api", Public: true},
|
||||
{PathPrefix: "/api/billing/plans", Upstream: billingURL, StripPrefix: "/api", Public: true},
|
||||
{PathPrefix: "/api/billing/addons", Upstream: billingURL, StripPrefix: "/api", Public: true},
|
||||
{PathPrefix: "/api/billing/", Upstream: billingURL, StripPrefix: "/api", Public: false},
|
||||
// Domain (requires auth).
|
||||
{PathPrefix: "/api/domain/", Upstream: domainURL, StripPrefix: "/api", Public: false},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user