Skip to content

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

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

waleedlatif1 wants to merge 7 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 a buyer registered to their account also sees status on /marketplace/aws. While that scope's license is pending or active, the billing plans chip and the upgrade page's plans are hidden. Those are UI gates: credit chips and teammate gates still link to the upgrade page, which then explains billing is through Marketplace, and the Chat plan gate is unchanged, because the buyer is told to wait for setup.
  • 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 (registration, page, claim and admin routes 404, status lists are empty, 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: 64 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: 34 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.

1 Skipped Deployment
Project Deployment Actions Updated
docs Skipped Skipped Nov 6, 2026 6:00am UTC

Request Review

@greptile-apps

greptile-apps Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issues remain from this review.

Summary

Adds AWS Marketplace registration, buyer setup, license checks, subscription status, and durable email notices. Provisioning stays manual.

  • AWS Marketplace buyers can claim a license and register it to an account or organization.
  • AWS Marketplace licenses stay in sync with agreement events and periodic checks.
  • Billing pages show AWS licenses and hide plan choices for Marketplace-billed scopes.
  • Admins can review unlinked AWS licenses and link them to an organization.

Diagram

sequenceDiagram
  participant Buyer
  participant Sim
  participant AWS
  participant DB as Database and outbox
  participant Staff
  Buyer->>Sim: Submit Marketplace registration token
  Sim->>AWS: Resolve buyer and read entitlements
  Sim->>DB: Save license and one-time claim
  Sim-->>Buyer: Open setup page
  Buyer->>Sim: Register account or link organization
  Sim->>AWS: Read entitlements again
  Sim->>DB: Lock license, consume claim, queue notices
  DB-->>Staff: Send provisioning notice
  Staff->>Sim: Provision Enterprise with payment collection paused
Loading

Reviews (5) · Last reviewed commit: "fix(billing): keep ended agreements queu..." · 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

@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.

@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 47 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.
Keep License Updated events for unknown licenses queued until AWS describes
them, so a renewal cannot be lost. Tell the team when the event reaches a
license already taken, and when a repeat take changes the contact details.
Give the cancel instruction its required operationId, and only when an
organization holds the license.

Show account-registered licenses on organization billing, and stop offering
plans on the upgrade page to anyone billed through AWS. Delete the expired
and inactive claim states, which no reachable row can produce.
…ope account status

A retried notice could tell staff to cancel a license that had since recovered, and a notice for a
lapsed license could tell them to provision it, so next steps now read the license as it stands. A
viewer's account license no longer labels an organization's Stripe billing as Marketplace-billed,
and upgrade entry points stay hidden until the lookup settles. A buyer registered to their account
sees status on the onboarding page itself, since they may not be able to open any billing page.
Account license reads are keyed by viewer, reverify is scoped to the configured product, lapsed
dimension ties resolve deterministically, and the admin routes honor the feature switch.
@waleedlatif1
waleedlatif1 force-pushed the feat/aws-marketplace-saas branch from ce2c853 to 3277b99 Compare November 6, 2026 05:03
@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.

No issues found across 49 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Turn on auto-fix | Re-trigger cubic

… each message

An Agreement Ended event can arrive before the entitlement read reflects the end, and deleting it
left the license current for up to a day, so the message now stays queued until AWS reports the
license ended. The run budget is checked before each message, since one ended agreement can read
many licenses, and messages not started stay queued for the next run. Removes a billing redirect
nothing uses any more, and stops one test depending on the order of same-transaction notices.
@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.

No issues found across 47 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.

Turn on auto-fix | Re-trigger cubic

This branch was previously deployed

1 inactive deployment
Preview — e2027bd6 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