Skip to content

Parameterized triggers, and support for application_fee events#1463

Open
mnorth-stripe wants to merge 1 commit intomasterfrom
wave1-pr12-application-fee
Open

Parameterized triggers, and support for application_fee events#1463
mnorth-stripe wants to merge 1 commit intomasterfrom
wave1-pr12-application-fee

Conversation

@mnorth-stripe
Copy link
Copy Markdown
Contributor

Reviewers

r? @
cc @stripe/developer-products

Important

There are some critical decisions in this PR description. It's long, but intentionally detailed for a reason

Summary

Adds support for triggering application fee events and introduces a --param flag with pre-flight validation for triggers that require user-provided configuration.

Background

Some Stripe events require configuration that can't be pre-filled in fixtures. For example, application fee events require a Connect account ID with the transfers capability enabled. Previously, these fixtures would:

  • Fail at API call time with unclear errors
  • Require users to manually edit fixture JSON
  • Have no validation until the API request was made

This PR addresses this by adding a --param flag that validates required parameters before making API calls, providing early feedback and clear error messages.

What's Included

New --param Flag

Adds pre-flight validation for trigger parameters:

# Provide required parameters validated before API calls
stripe trigger application_fee.created \
  --param charge:transfer_data.destination=acct_123

# Clear error if parameter is missing
stripe trigger application_fee.created
# Error: ✘ Missing required parameters
#
#   charge:transfer_data.destination - Connect account ID with transfers capability enabled
#   Example:
#
#      stripe trigger application_fee.created \
#         --param charge:transfer_data.destination=acct_1ABC234DEF567GHI

Features:

  • Uses same syntax as --override: fixtureName:path.to.field=value
  • Validates against required_params metadata in fixture files
  • Explicit errors for malformed syntax (missing =, empty values)
  • Parameters take precedence over --override values
  • Help text shows which events require parameters

New Application Fee Triggers

Adds 3 new Connect-related event triggers:

  • application_fee.created - When an application fee is created on a charge
  • application_fee.refunded - When an application fee is fully refunded
  • application_fee.refund.updated - When an application fee refund is updated

All three require a Connect account ID via the new --param flag.

Implementation Details

  • Fixture metadata: Added required_params array to _meta block in fixture JSON
    • Field renamed from example to placeholder for semantic clarity
  • Validation logic: New ValidateRequiredParams() function with comprehensive error messages
    • Error messages include the actual event name in usage examples
    • Example error output:
      ✘ Missing required parameters
      
        charge:transfer_data.destination - Connect account ID
        Example:
      
           stripe trigger application_fee.created \
              --param charge:transfer_data.destination=acct_123
      
  • Test coverage: 15 tests (11 unit tests for validation logic, 4 integration tests with fixture execution)
  • Help text: Shows parameter requirements inline with event names. Vertical alignment is calculated from events that require params only (not all events), keeping the --param column tight at 82 chars instead of 93.

Output Changes

stripe trigger --help: Event list

Three new application fee events appear in the event list. Events that require parameters show the --param syntax inline, vertically aligned. Alignment is calculated only from events with params (30 chars), keeping lines at 82 chars instead of 93:

 Supported events:
   account.application.deauthorized
   account.updated
+  application_fee.created         --param charge:transfer_data.destination=<value>
+  application_fee.refund.updated  --param charge:transfer_data.destination=<value>
+  application_fee.refunded        --param charge:transfer_data.destination=<value>
   balance.available
   billing_portal.configuration.created
   billing_portal.configuration.updated
   billing_portal.session.created
   cash_balance.funds_available

stripe trigger --help: Examples section

The examples section now includes descriptive comments, a parameterized usage example, and blank line separation between examples:

 Examples:
-  stripe trigger payment_intent.created
+  # Trigger a basic event
+  stripe trigger payment_intent.created
+
+  # Trigger an event that requires parameters
+  stripe trigger application_fee.created --param charge:transfer_data.destination=acct_123

 Flags:
       --add stringArray         Add params to the trigger
       --api-version string      Specify API version for trigger
       --edit                    Edit the trigger directly in your default IDE
   -h, --help                    help for trigger

stripe trigger --help: New --param flag

A new --param flag for pre-flight parameter validation appears between --override and --raw:

       --override stringArray    Override params in the trigger
+      --param stringArray       Set required parameters (validated before
+                                execution)
       --raw string              Raw fixture in string format to replace
                                 all default fixtures
       --remove stringArray      Remove params from the trigger

Spicy Decision: Params as a Layer on Top of Overrides

This PR deliberately does not introduce a formal concept of "params" in the fixture system itself. Instead, --param is a validation layer on top of the existing --override mechanism — params use the exact same syntax and are merged into overrides before execution.

This means fixture files remain unchanged at the execution level. The _meta.required_params block is only read by the trigger command for validation and help text. The fixture runner doesn't know the difference between a --param and an --override.

The question for reviewers: Is this the right approach, or should we formalize parameterization as a first-class concept in the fixture system?

Arguments for the current approach:

  • It works today with zero changes to the fixture runner
  • Users get pre-flight validation and actionable errors right now
  • The --param / --override distinction is a UX concern, not a data model concern

Arguments for formalizing params in fixtures:

  • Other consumers of fixtures (not just stripe trigger) could benefit from the same validation
  • The validation logic currently lives in triggers.go — if fixtures supported params natively, this would move to the fixture layer where it arguably belongs

What can be deferred vs. what needs to be decided now?

The refactoring question — whether validation lives in triggers.go or moves down into the fixture runner — is entirely internal. If we later want fixtures to support parameterization as a general concept, that refactoring can be done without breaking users who have already started using --param. The flag, its syntax, and its behavior all stay the same; only the internal plumbing moves.

However, there are decisions in this PR that are hard to change later because they form the user-facing contract:

  1. Is --param the right name for this concept? Once users start scripting against it, renaming is a breaking change.

  2. Should params use fixture JSON paths (charge:transfer_data.destination) or semantic names (connected_account_id)? I considered semantic names earlier — they're shorter and describe the purpose of an input (similar to function argument names). But the downside is you lose the relationship to where that value is placed in the API call, and that relationship is a fairly central part of the fixture mental model. This PR uses fixture paths, which means users can look at the fixture JSON and see exactly where their value ends up — same as --override.

Testing

# Run fixture tests
go test ./pkg/fixtures/... -v

# Test the trigger command
stripe trigger application_fee.created --param charge:transfer_data.destination=acct_test123

All tests pass. The --param flag provides robust validation before API calls, catching configuration errors early with actionable error messages.

@mnorth-stripe mnorth-stripe requested a review from a team as a code owner March 8, 2026 20:46
@mnorth-stripe mnorth-stripe force-pushed the wave1-pr12-application-fee branch from 7804968 to eccf8e0 Compare March 8, 2026 20:50
Copy link
Copy Markdown
Contributor

@vzhang-stripe vzhang-stripe left a comment

Choose a reason for hiding this comment

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

Minor Observations:

  1. Parameter parsing: The code assumes --param follows the fixtureName:path=value format. Consider what happens if someone uses --param with an event
    that doesn't match the fixture name (e.g., stripe trigger application_fee.created --param invoice:...). The error handling might be confusing.
  2. Help text alignment: The vertical alignment logic in EventList() looks good but could be fragile if event names or parameter descriptions get very
    long. Consider max-width handling.
  3. Metadata loading: getRequiredParamsForEvent() loads and parses the fixture file. Consider whether this could fail gracefully if a fixture is
    malformed (though this is likely fine for a CLI tool).

Copy link
Copy Markdown
Contributor

@vzhang-stripe vzhang-stripe left a comment

Choose a reason for hiding this comment

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

1. Required --param values are ignored by RPC callers

In pkg/rpcservice/trigger.go, the RPC path hardcodes []string{} for the new param argument instead of forwarding anything
from the request:

req.Override,
[]string{}, // param - not yet supported in RPC, can be added later
req.Add,

That means application_fee.* triggers can work from the CLI but will always fail validation when invoked through RPC,
because those events now require charge:transfer_data.destination. This creates inconsistent behavior across entry points
and can break any existing RPC-based integrations that call Trigger.

Why this matters: the feature is user-facing, but only partially wired through the product surface. If RPC intentionally
doesn’t support params yet, the validation layer probably shouldn’t apply there, or the RPC schema should be updated in the
same change.

2. --override is applied twice, which can change behavior unexpectedly

BuildFromFixtureFile still calls NewFixtureFromFile(..., override, add, remove, ...), and then later merges override again
together with param:

fixture, err := NewFixtureFromFile(..., override, add, remove, edit)
...
mergedOverrides = append(mergedOverrides, override...)
mergedOverrides = append(mergedOverrides, param...)
if len(mergedOverrides) > 0 {
if err := fixture.Override(mergedOverrides); err != nil {
return nil, err
}
}

So overrides are now processed once inside NewFixtureFromFile and then a second time afterward. For many values that’s
harmless, but it’s still a semantic change and can become problematic for nested merges or future override behaviors. If
the goal is “params take precedence over overrides,” the cleaner fix is to either:

  • stop passing override into NewFixtureFromFile and apply the merged list once afterward, or
  • keep the existing flow and apply only param afterward.

Copy link
Copy Markdown
Collaborator

@tomer-stripe tomer-stripe left a comment

Choose a reason for hiding this comment

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

Was going through the pr -- I wonder if it makes more sense to use --add in this case instead of creating a new flag (for --param) but still keeping the pre-flight validation.

Semantically --override assumes you're squashing over an existing value but we added --add to supplement the trigger with your own data. It feels similar to what you're doing with --param minus the pre-flight validation.

@@ -0,0 +1,161 @@
## Summary
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can you remove this?

@mnorth-stripe mnorth-stripe force-pushed the wave1-pr12-application-fee branch from 2b0d776 to f54ceec Compare April 14, 2026 16:34
Adds a --param flag to `stripe trigger` with pre-flight validation
for events that require user-provided configuration. Application fee
events are the first to use this, requiring a Connect account ID.

The --param flag validates required parameters before making API calls,
providing early feedback and clear error messages. It uses the same
syntax as --override (fixtureName:path.to.field=value) and is backed
by a new required_params metadata block in fixture JSON files.

New triggers: application_fee.created, application_fee.refunded,
application_fee.refund.updated.
@mnorth-stripe mnorth-stripe force-pushed the wave1-pr12-application-fee branch from f54ceec to f6ca7ba Compare April 14, 2026 21:25
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.

3 participants