Skip to content

Production Release 2026-02-10#2995

Merged
clD11 merged 16 commits intoprodfrom
master
Feb 10, 2026
Merged

Production Release 2026-02-10#2995
clD11 merged 16 commits intoprodfrom
master

Conversation

@clD11
Copy link
Contributor

@clD11 clD11 commented Feb 10, 2026

WIP

renovate bot and others added 16 commits November 26, 2025 21:32
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…2978)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…5.1.1 (#2976)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…2981)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…rs table (#2974)

* fix(skus): Add indexes for externalID and radomSubscriptionId in orders table

* Update CurrentMigrationVersion to 73
…activate order (#2990)

* refactor: add support for stipe payment intent succeeded webhook and activate order

* test: add parse test for stripe payment intent notification

* refactor: rename is perpetual license to is one off payment

* refactor: remove extra new line
@github-actions
Copy link

[puLL-Merge] - brave-intl/bat-go@2995

Description

This PR introduces support for Stripe one-off payments (perpetual licenses) in the SKU service, alongside CI/infrastructure improvements and dependency updates.

Core feature: When a Stripe payment_intent.succeeded webhook is received, the system now recognizes it as a perpetual license activation event. It validates the order is a one-off payment type (brave-origin-premium-perpetual-license), then activates it with a 100-year expiration. The Stripe checkout session mode is dynamically determined based on whether order items have a "one-off" period (using payment mode) vs. the default subscription mode.

Other changes:

  • Renames renewOrderWithExpPaidTimeupdateOrderWithExpPaidTime for more accurate semantics (it's used for both renewals and initial activations)
  • Adds two new database migrations (0072, 0073) creating indexes on orders.metadata->>'externalID' and orders.metadata->>'radomSubscriptionId'
  • Consolidates 5 separate golangci-lint CI jobs into a single matrix-based job
  • Bumps GitHub Actions versions, AWS SDK, and golangci-lint action versions
  • Adds a make migrate-create helper and a migrate Docker Compose service
  • Adds documentation for creating migrations in README

Possible Issues

  1. ubuntu-slim runner may not exist: The new lint aggregation job in golangci-lint.yml uses runs-on: ubuntu-slim, which is not a standard GitHub-hosted runner label. This will likely cause the job to hang waiting for a runner or fail. It should probably be ubuntu-latest.

  2. CONCURRENTLY index creation in migrations may fail with migrate tool: CREATE INDEX CONCURRENTLY cannot run inside a transaction, but the golang-migrate tool wraps each migration in a transaction by default. This will cause migrations 0072 and 0073 to fail unless the migration runner is configured to skip transactions for these files.

  3. 100-year expiration as "perpetual": paidt.AddDate(100, 0, 0) is a pragmatic approach but is a magic number with no constant or documentation explaining the rationale. If business rules change, this is easy to miss.

  4. services/go.sum contains duplicate entries: The go.sum file retains both old and new versions of many AWS SDK packages (e.g., both v1.39.0 and v1.41.1 of aws-sdk-go-v2). While Go tolerates this, it suggests an incomplete go mod tidy.

  5. determineStripeCheckoutSessionMode only checks single-item one-off: If there are 2+ items with period: "one-off", it defaults to subscription mode. The test confirms this is intentional but it could be surprising behavior.

Security Hotspots

  1. Stripe webhook event validation: The payment_intent.succeeded handler trusts the orderID from paymentIntent.Metadata. While Stripe webhook signature verification presumably happens upstream, if an attacker could craft a valid webhook with an arbitrary orderID, they could activate any order as a perpetual license. The IsOneOffPayment() check mitigates this partially, but only for orders that happen to have the perpetual license SKU variant.

  2. No idempotency check on perpetual license activation: If the same payment_intent.succeeded event is delivered multiple times (Stripe retries), activateStripePL will be called repeatedly. While the operations are mostly idempotent (set status, append metadata), there's no explicit guard against duplicate processing.

Changes

Changes

.github/workflows/golangci-lint.yml

  • Consolidated 5 separate lint jobs (golangci-libs, golangci-services, golangci-tools, golangci-cmd, golangci-main) into a single matrix job golangci
  • Added an aggregated lint status check job (but uses ubuntu-slim which may not exist)
  • Updated action versions

.github/workflows/ci.yml, codeql-analysis.yml, generalized-deployments.yaml

  • Bumped actions/checkout, actions/setup-go, aws-actions/configure-aws-credentials, github/codeql-action to newer versions

Makefile

  • Added migrate-create target for creating new migration files via Docker Compose

README.md

  • Added documentation section on creating new migrations

docker-compose.dev.yml

  • Added migrate service using migrate/migrate:v4.19.0 with security hardening (no-new-privileges, read_only)

libs/datastore/postgres.go

  • Bumped CurrentMigrationVersion from 71 to 73

migrations/0072_*.sql, migrations/0073_*.sql

  • New indexes on orders.metadata->>'externalID' and orders.metadata->>'radomSubscriptionId' (created concurrently, with partial index WHERE NOT NULL)

services/skus/model/model.go

  • Added ErrOrderNotOneOffPayment error constant
  • Added Order.IsOneOffPayment() method (checks single item with perpetual license SKU)
  • Added OrderItem.IsOriginPL() method

services/skus/stripe.go

  • Added paymentIntent field to stripeNotification
  • Added parsing of payment_intent.succeeded events
  • Added shouldActivatePL(), paymentID(), paidAt() methods
  • Updated ntfSubType() and effect() for payment intent handling

services/skus/service.go

  • Renamed renewOrderWithExpPaidTime[Tx]updateOrderWithExpPaidTime[Tx]
  • Added activateStripePL() for activating perpetual license orders
  • Added determineStripeCheckoutSessionMode() to select payment vs subscription mode
  • Modified createStripeSession to accept and use dynamic checkout session mode
  • Modified createStripeSession to conditionally set SubscriptionData or PaymentIntentData based on mode
  • Updated processStripeNotificationTx with new shouldActivatePL case
  • Updated paidOrderCreator interface to use renamed method

services/skus/testdata/stripe_payment_intent_succeeded.json

  • New test fixture for payment intent succeeded webhook

go.mod/go.sum (libs, main, services, tools)

  • Updated AWS SDK Go v2 from v1.39.0 to v1.41.1 and related sub-packages
  • Added aws/aws-sdk-go-v2/service/signin as new dependency
sequenceDiagram
    participant Stripe
    participant WebhookHandler
    participant Service
    participant OrderRepo
    participant PayHistRepo

    Stripe->>WebhookHandler: payment_intent.succeeded event
    WebhookHandler->>Service: processStripeNotificationTx(ntf)
    Service->>Service: parseStripeNotification(event)
    Service->>Service: ntf.shouldActivatePL() → true
    Service->>Service: ntf.orderID() → extract from metadata
    Service->>OrderRepo: getOrderFullTx(orderID)
    OrderRepo-->>Service: order (with items)
    Service->>Service: order.IsOneOffPayment() → check SKU variant
    alt Not one-off payment
        Service-->>WebhookHandler: ErrOrderNotOneOffPayment
    end
    Service->>Service: ntf.paymentID() → payment intent ID
    Service->>Service: ntf.paidAt() → created timestamp
    Service->>Service: expt = paidAt + 100 years
    Service->>Service: activateStripePL(order, paymentID, paidAt, expt)
    Service->>OrderRepo: SetStatus(orderID, "paid")
    Service->>OrderRepo: SetExpiresAt(orderID, expt)
    Service->>PayHistRepo: Insert(orderID, paidAt)
    Service->>OrderRepo: AppendMetadata("paymentProcessor", "stripe")
    Service->>OrderRepo: AppendMetadata("stripePaymentId", paymentID)
    Service-->>WebhookHandler: success
Loading

Sneagan

This comment was marked as duplicate.

Copy link
Contributor

@Sneagan Sneagan left a comment

Choose a reason for hiding this comment

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

Components reviewed previously.

@clD11 clD11 added this pull request to the merge queue Feb 10, 2026
Merged via the queue into prod with commit 92b4dbb Feb 10, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants