Repository navigation
feat(billing): AWS Marketplace SaaS contract registration and entitlement verification - #9726
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
[Medium impact] The PR appears safe to merge; no actionable issues remain from this review. SummaryAdds AWS Marketplace registration, buyer setup, license checks, subscription status, and durable email notices. Provisioning stays manual.
|
5abc97d to
54b6fe8
Compare
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 36 files
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
54b6fe8 to
ce2c853
Compare
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
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.
ce2c853 to
3277b99
Compare
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
… 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.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
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
Summary
POST /api/marketplace/aws/register:x-amzn-marketplace-token. The route callsResolveCustomer, rejects other products, and identifies buyers byCustomerAWSAccountId+LicenseArn; AWS no longer returnsCustomerIdentifierfor new products.GetEntitlementsfiltered byLICENSE_ARN, upserts a row keyed by license ARN (one account can hold several, per concurrent agreements), and always 303s to/marketplace/aws./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.pausePaymentCollection, because AWS bills these customers. No entitlement or Stripe code changes.applyVerificationunder 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./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.GET /api/cron/aws-marketplace-reconcile:GetEntitlementsrather than trusting the event. Poison messages and agreements we don't hold are discarded; transient failures stay for redelivery and the DLQ.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.GET/POST /api/v1/admin/dashboard/aws-marketplace-licenseslists unlinked licenses and links one, with audit.AWS_MARKETPLACE_PRODUCT_CODEis 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_URLenables the drain. The AWS side is in the infra repo.0454_aws_marketplace_license: a new table, enum and indexes only.Type of Change
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.tsandoutbox/handlers.test.ts: 34 pass.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.InvalidTokenException, and a cancelled agreement returns no entitlements. A real rule delivered the raw event JSON to an SSE-SQS queue.Checklist
test-auditauthoring gate)