Skip to content

feat(billing): AWS Marketplace SaaS contract registration and entitlement verification - #9726

Open
waleedlatif1 wants to merge 4 commits into
stagingfrom
feat/aws-marketplace-saas
Open

waleedlatif1 wants to merge 4 commits into
stagingfrom
feat/aws-marketplace-saas

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • AWS Marketplace SaaS contract integration, so the SaaS listing can pass AWS's public-visibility audit. AWS requires successful Marketplace Entitlement Service calls from a real buyer flow.
  • Fulfillment URL POST /api/marketplace/aws/register:
    • AWS posts x-amzn-marketplace-token. The route calls ResolveCustomer, rejects other products, and identifies buyers by CustomerAWSAccountId + LicenseArn; AWS no longer returns CustomerIdentifier for new products.
    • It calls GetEntitlements filtered by LICENSE_ARN, upserts a row keyed by license ARN (one account can hold several, per concurrent agreements), and always 303s to /marketplace/aws.
    • An active or still-confirming license gets a one-time claim: an httpOnly cookie, hash stored, valid 4h. A future-dated agreement with no ARN yet gets its own message. The token is never logged.
  • Onboarding /marketplace/aws: the buyer signs in or signs up, confirms a contact email (optional company), and links the license to an org they own or administer, or registers it to their account if they have none.
    • Both actions re-verify the entitlement and recheck the claim under a row lock; a license belongs to at most one org.
    • The card shows the masked AWS account and the signed-in email, with no preselected org.
    • If AWS hasn't confirmed yet, the buyer sees that state and can check again; linking stays refused until a fresh read is active.
  • Provisioning stays manual, on AWS's documented path: the buyer is told their account is being set up and to wait to be contacted, with the turnaround and a support contact, on screen and by email. Staff provision through the existing Enterprise flow with pausePaymentCollection, because AWS bills these customers. No entitlement or Stripe code changes.
  • Durable notices: team and buyer notices go through the transactional outbox, in the same transaction as the state change. One applyVerification under the row lock serves every writer, so any change to status, dimension, quantity or expiry notifies the team, and an older read never overwrites a newer one.
  • Subscription status: billing shows licenses at org and personal scope, and hides upgrade entry points while one is pending or active.
  • Reconcile cron GET /api/cron/aws-marketplace-reconcile:
    • Drains license and agreement events from SQS and re-reads GetEntitlements rather than trusting the event. Poison messages and agreements we don't hold are discarded; transient failures stay for redelivery and the DLQ.
    • Replacements and renewals key on previousAgreementId: the new license inherits org, linker and contact in either arrival order, and a superseded license never tells staff to cancel. Advisory events notify the team.
    • Re-verifies licenses not attempted in 24h. The run has a wall-clock budget inside the lock and visibility timeouts.
  • Admin: GET/POST /api/v1/admin/dashboard/aws-marketplace-licenses lists unlinked licenses and links one, with audit.
  • Credentials and limits: Marketplace and SQS calls sign as the ECS task role via container credentials, not the app's static user keys, with connection and request timeouts and SDK-default retries.
  • Feature switch: the feature is off unless AWS_MARKETPLACE_PRODUCT_CODE is set (routes 404, cron skips), so self-hosted and non-prod envs are unaffected. AWS_MARKETPLACE_EVENTS_QUEUE_URL enables the drain. The AWS side is in the infra repo.
  • Migration 0454_aws_marketplace_license: a new table, enum and indexes only.

Type of Change

  • New feature

Testing

  • bun run test:integration aws-marketplace/lifecycle.integration.ts: 55 pass against real Postgres and Redis, including a real two-tab race, stale reads, outbox retry, pending→active, renewals in both orders, a late duplicate renewal, admin authorization, and org and account read isolation.
  • entitlements.test.ts and outbox/handlers.test.ts: 33 pass.
  • Red-proofed by reverting each guard, in groups and in isolation where a group masked one.
  • tsc --noEmit -p apps/sim, bun run lint, check:migrations origin/staging, drizzle-kit generate (no drift), plus api-validation:strict, api-contract-routes, route-verbs, source-conventions, boundaries, principal-kind-parity, permission-group-enforcement, application-graph, react-query, client-boundary, schema-mock, comment-hygiene, test-patterns.
  • Real AWS, read-only from the seller account: a bad token raises InvalidTokenException, and a cancelled agreement returns no entitlements. A real rule delivered the raw event JSON to an SSE-SQS queue.
  • Not yet exercised: a real token with an active entitlement, the pages in a browser, and task-role signing on ECS. Those need a Limited-listing test purchase after deploy.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing (new tests pass the test-audit authoring gate)
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@waleedlatif1
waleedlatif1 requested a review from a team as a code owner November 6, 2026 02:46
@vercel

vercel Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Building Building Preview Nov 6, 2026 4:29am UTC

Request Review

@greptile-apps

greptile-apps Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The changes add a bounded AWS Marketplace contract workflow and recurring license checks.

Fix the missing change notice on re-registration before merging.

Findings

  1. P1 Re-registration hides purchase changes ▶
  2. P2 Email waits escape the deadline ▶

Summary

Adds AWS Marketplace contract registration, buyer setup, read-only license status, and queued entitlement checks. Enterprise provisioning remains manual.

  • AWS buyers can register contract licenses from the Marketplace.
  • Buyers can claim a license and choose where it is registered.
  • Scheduled checks refresh Marketplace license status and agreement details.
  • Organization billing settings show linked Marketplace license terms.

Diagram

sequenceDiagram
  participant Buyer
  participant Sim
  participant AWS
  participant DB
  participant Team
  AWS->>Sim: Registration token through buyer's browser
  Sim->>AWS: ResolveCustomer and GetEntitlements
  Sim->>DB: Save license and claim hash
  Sim-->>Buyer: Redirect with claim cookie
  Buyer->>Sim: Sign in and choose account or organization
  Sim->>AWS: Recheck entitlement
  Sim->>DB: Lock license and consume claim
  Sim->>Team: Request manual provisioning
  Sim-->>Buyer: Confirm setup
  Sim->>AWS: Read queued events and recheck licenses
  Sim->>DB: Save current terms
  Sim->>Team: Report detected changes
Loading

Reviews (2) · Last reviewed commit: "fix(billing): register new-product AWS M..." · Reviewed by Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 32 files

Turn on auto-fix | Re-trigger cubic

Comment thread packages/db/schema.ts Resolved
@waleedlatif1
waleedlatif1 force-pushed the feat/aws-marketplace-saas branch from 5abc97d to 54b6fe8 Compare November 6, 2026 03:37
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 36 files

Reply with feedback, questions, or to request a fix.

Turn on auto-fix | Re-trigger cubic

…ing, and entitlement reconcile

- POST /api/marketplace/aws/register: the listing's fulfillment URL. Resolves the
  registration token with ResolveCustomer, rejects a foreign product, calls
  GetEntitlements for the license on every registration, upserts the license by ARN,
  and 303s to /marketplace/aws with a one-time httpOnly claim cookie when active.
- /marketplace/aws onboarding page: sign in or sign up, review the license, and link
  it to an organization the viewer owns or administers.
- GET /api/marketplace/aws/claim and POST /api/marketplace/aws/claim/link: contracted
  internal routes over application use cases. Linking requires the open claim and org
  admin, re-verifies the entitlement, refuses a license linked elsewhere, consumes the
  claim, audits the link, and emails the enterprise inbox. No subscription changes.
- GET /api/cron/aws-marketplace-reconcile: drains agreement events from SQS in bounded
  batches and re-verifies licenses not checked within a day, under a Redis lock.
- aws_marketplace_license table (migration 0454); AWS_MARKETPLACE_PRODUCT_CODE and
  AWS_MARKETPLACE_EVENTS_QUEUE_URL env vars. With no product code every surface is off.
…gn Marketplace calls as the task role

- POST /api/marketplace/aws/claim/register-account: a session-only application operation
  for a buyer who administers no organization (or prefers not to pick one). Same guards as
  linking: an open claim for this license, rechecked under the row lock, and a fresh
  entitlement read that must be current. Refuses a license already linked to an
  organization. Records the buyer as linkedBy with no organization, consumes the claim,
  clears the cookie, audits aws_marketplace_license.registered, and emails the enterprise
  inbox to create the buyer's organization. Such a license can still be linked later.
- Onboarding page: registering is the primary action with no administered organization
  and a secondary one beside the organization picker; a registered success state.
- Marketplace and event-queue clients use the ECS container credential provider when the
  container credential endpoint is present, since the app env also carries static user
  keys the default chain would prefer; elsewhere they keep the default chain.
…isting guidelines, and handle agreement events

- ResolveCustomer leaves CustomerIdentifier empty for new SaaS products, so registration now
  requires the account ID and keys off it and the license ARN. customer_identifier is nullable,
  customer_aws_account_id is required (not unique), and agreement_id is stored. No license ARN
  yet means a future-dated agreement: nothing is saved and the buyer is told to return on the
  start date.
- Listing guidelines: the claim step collects a contact email and optional company, the buyer
  gets a confirmation with turnaround and support contact, organization members see subscription
  status on the billing page through a contracted read, and the team is emailed when a license's
  status, quantity, or expiry changes, or when an event names a license nobody registered.
- Agreement events: Ended re-verifies the licenses of that agreement; a RENEW tells the team once
  across both seller roles and links nothing; agreements for other products are dropped quietly.
- Every writer of entitlement fields honours the newest read, so a slow registration or claim
  cannot restore a lapsed license, and a claim is refused if a newer read says inactive.
- Post-commit notices, lookups included, never fail the request. Re-verifies order by last attempt,
  so failing licenses cannot starve the batch. The reconcile has a 6-minute wall-clock budget and
  a per-message abort that fit the receive visibility timeout and the cron lock.
- The fulfillment URL redirects a GET to onboarding and accepts multipart forms under the same cap.
… across agreement replacements

- Team notices and the buyer confirmation go through the outbox, enqueued in the transaction that
  changes the license, so a failed send is retried and reconcile never waits on email. Every writer
  decides under its row lock whether terms moved, dimension included, and a new registration tells
  the team at once.
- A license with no entitlements yet is pending, not lapsed: it holds a claim, the buyer can leave
  contact details, and taking it waits for a read that says it is current. The notice reports
  whether the License Updated event has arrived. A Deprovisioned event the read contradicts stays
  queued, and a verification recording inactive clears the claim.
- Agreement replacement keys on previousAgreementId, works in either arrival order, hands the
  owner on to the new license, refuses a late A to B over B to C, and uses the event time so a
  redelivered license event cannot roll the agreement back. A superseded license ending does not
  ask the team to cancel. Advisory events reach the team.
- Provisioning stays manual. The buyer is told the account is being set up, to wait, the
  turnaround, and the contact. The team notice says to provision with payment collection paused
  and never to invoice through Stripe, and names the cancel flow for a lapse. An admin route lists
  unlinked licenses and links one, with audit.
- Status for account-only buyers, and billing hides plan entry points while a license is active.
  The link card shows a masked account and the signed-in email with no preselected organization,
  and the claim lasts 4 hours.
- Clients use request timeouts and SDK default retries, signing through fromHttp from
  @aws-sdk/credential-providers. Each license of an ended agreement gets its own read bound. The
  fulfillment URL always answers with a redirect and takes only URL-encoded forms.
@waleedlatif1
waleedlatif1 force-pushed the feat/aws-marketplace-saas branch from 54b6fe8 to ce2c853 Compare November 6, 2026 04:28
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

This branch was successfully deployed

1 active (outdated) deployment
Preview — 54b6fe87 Deployed Nov 6, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant