LatticeStripe.Testing (LatticeStripe v1.7.5)

Copy Markdown View Source

Test helpers for apps using LatticeStripe.

This module ships with the LatticeStripe hex package so downstream users can construct realistic Stripe webhook events in their test suites without needing to know Stripe's HMAC signing scheme.

Usage

# High-level: get a typed %Event{} struct for testing event handlers
import LatticeStripe.Testing

test "handles payment_intent.succeeded webhook" do
  event = generate_webhook_event("payment_intent.succeeded", %{
    "id" => "pi_test_123",
    "amount" => 2000,
    "currency" => "usd",
    "status" => "succeeded"
  })

  assert {:ok, :processed} = MyApp.Webhooks.handle(event)
end

# Low-level: get a signed raw payload + header for Plug-level testing
test "verifies webhook signature via Plug" do
  {payload, sig_header} = generate_webhook_payload(
    "customer.created",
    %{"id" => "cus_test_456", "email" => "new@example.com"},
    secret: "whsec_test_secret"
  )

  conn =
    Plug.Test.conn(:post, "/webhooks", payload)
    |> Plug.Conn.put_req_header("stripe-signature", sig_header)
    |> MyApp.Router.call([])

  assert conn.status == 200
end

Important Note

This module is intended for use in test environments only. It ships in lib/ (not test/support/) so downstream users can import it without configuring custom elixirc_paths — but it has no side effects and is safe to include in production releases.

Summary

Functions

Converts a canonical CreditNote fixture map into %LatticeStripe.CreditNote{}.

Converts a canonical Dispute fixture map into %LatticeStripe.Dispute{}.

Converts a canonical thin-event notification fixture map into %LatticeStripe.EventNotification{}.

Converts a canonical File fixture map into %LatticeStripe.File{}.

Converts a canonical FileLink fixture map into %LatticeStripe.FileLink{}.

Generates a signed thin-event webhook payload pair for Plug-level testing.

Builds a %LatticeStripe.Event{} struct for the given event type and object data.

Generates a signed webhook payload pair for Plug-level testing.

Converts a canonical Mandate fixture map into %LatticeStripe.Mandate{}.

Converts a canonical Quote fixture map into %LatticeStripe.Quote{}.

Converts a canonical SetupAttempt fixture map into %LatticeStripe.SetupAttempt{}.

Converts a canonical Tax Calculation fixture map into %LatticeStripe.Tax.Calculation{}.

Converts a canonical TaxId fixture map into %LatticeStripe.TaxId{}.

Converts a canonical Tax Transaction fixture map into %LatticeStripe.Tax.Transaction{}.

Functions

credit_note(raw_map)

@spec credit_note(map()) :: LatticeStripe.CreditNote.t()

Converts a canonical CreditNote fixture map into %LatticeStripe.CreditNote{}.

dispute(raw_map)

@spec dispute(map()) :: LatticeStripe.Dispute.t()

Converts a canonical Dispute fixture map into %LatticeStripe.Dispute{}.

event_notification(raw_map)

@spec event_notification(map()) :: LatticeStripe.EventNotification.t()

Converts a canonical thin-event notification fixture map into %LatticeStripe.EventNotification{}.

Direct typed-builder parallel to dispute/1, customer/1, etc. — use this when your test needs a typed %EventNotification{} value without going through signature verification.

Example

import LatticeStripe.Test.Fixtures.EventNotification

notif = LatticeStripe.Testing.event_notification(event_notification_map())
assert notif.type == "v2.core.account.updated"

When you need a signed wire-format payload (for example to exercise a Phoenix controller calling Webhook.parse_event_notification/4), use generate_thin_event_payload/3 instead.

file(raw_map)

@spec file(map()) :: LatticeStripe.File.t()

Converts a canonical File fixture map into %LatticeStripe.File{}.

file_link(raw_map)

@spec file_link(map()) :: LatticeStripe.FileLink.t()

Converts a canonical FileLink fixture map into %LatticeStripe.FileLink{}.

generate_thin_event_payload(type, related_object_data \\ nil, opts)

@spec generate_thin_event_payload(String.t(), map() | nil, keyword()) ::
  {String.t(), String.t()}

Generates a signed thin-event webhook payload pair for Plug-level testing.

Thin-event counterpart to generate_webhook_payload/3 — produces a wire-format /v2/events notification payload (object: "v2.core.event", ISO 8601 created, no data or pending_webhooks keys) and a matching Stripe-Signature header. Returns {payload_string, signature_header_value} that round-trips through LatticeStripe.Webhook.parse_event_notification/4 without modification.

Use generate_webhook_payload/3 for snapshot v1 webhooks (construct_event/4) and this helper for thin-event v2 webhooks (parse_event_notification/4). Calling the wrong helper produces a structurally-valid payload that decodes to a mostly-nil struct — keep snapshot and thin-event test paths obviously distinct in your test suite.

Parameters

  • type - Stripe thin-event type string, e.g. "v2.core.account.updated"
  • related_object_data - The related_object map (%{"id" => ..., "type" => ..., "url" => ...}) or nil for snapshot-style v2 events (default: nil)
  • opts - Options:
    • :secret - Webhook signing secret (required; raises KeyError if absent)
    • :timestamp - Unix-seconds timestamp integer used both to sign the payload and to derive the ISO 8601 created field (default: current system time)
    • :id - Event ID string (default: "evt_test_" <> random_hex(16))
    • :context - Free-form context string (default: nil)
    • :livemode - boolean (default: false)

Returns

{raw_payload_string, stripe_signature_header_value} — the same shape as generate_webhook_payload/3.

Example

{payload, sig_header} =
  LatticeStripe.Testing.generate_thin_event_payload(
    "v2.core.account.updated",
    %{
      "id" => "acct_test_123",
      "type" => "v2.core.account",
      "url" => "/v2/core/accounts/acct_test_123"
    },
    secret: "whsec_test"
  )

{:ok, notif} =
  LatticeStripe.Webhook.parse_event_notification(payload, sig_header, "whsec_test")

assert notif.type == "v2.core.account.updated"
assert notif.related_object.id == "acct_test_123"

Pass nil for related_object_data to produce a snapshot-style v2 event (the notification will have related_object: nil; adopters dispatch these to Webhook.fetch_event/3 rather than fetch_related_object/3).

generate_webhook_event(type, object_data \\ %{}, opts \\ [])

@spec generate_webhook_event(String.t(), map(), keyword()) :: LatticeStripe.Event.t()

Builds a %LatticeStripe.Event{} struct for the given event type and object data.

Constructs a realistic Stripe event shape without making any HTTP calls. The data.object map is whatever you pass as object_data.

Parameters

  • type - Stripe event type string, e.g. "payment_intent.succeeded"
  • object_data - The data.object map for the event (default: %{})
  • opts - Options:
    • :id - Event ID string (default: "evt_test_" <> random_hex(16))
    • :api_version - API version string (default: "2026-03-25.dahlia")
    • :livemode - boolean (default: false)

Returns

A %LatticeStripe.Event{} struct.

Canonical raw maps from LatticeStripe.Testing.Fixtures.* compose directly with this helper.

Example

file = LatticeStripe.Testing.Fixtures.File.file_json()

event = LatticeStripe.Testing.generate_webhook_event(
  "payment_intent.succeeded",
  file
)
assert event.type == "payment_intent.succeeded"
assert event.data["object"]["id"] == file["id"]

generate_webhook_payload(type, object_data \\ %{}, opts)

@spec generate_webhook_payload(String.t(), map(), keyword()) ::
  {String.t(), String.t()}

Generates a signed webhook payload pair for Plug-level testing.

Returns {payload_string, signature_header_value} where the signature is computed using Webhook.generate_test_signature/3. The returned pair passes Webhook.construct_event/4 without modification.

The raw event map is JSON-encoded directly (before Event.from_map/1) to avoid round-trip encoding issues with the %Event{} struct.

Parameters

  • type - Stripe event type string, e.g. "customer.created"
  • object_data - The data.object map for the event (default: %{})
  • opts - Options:
    • :secret - Webhook signing secret (required)
    • :timestamp - Unix timestamp integer to embed in signature (default: current time)
    • Other opts (:id, :api_version, :livemode) are forwarded to generate_webhook_event/3

Returns

{raw_payload_string, stripe_signature_header_value}

Canonical raw maps from LatticeStripe.Testing.Fixtures.* compose directly with this helper.

Example

dispute = LatticeStripe.Testing.Fixtures.Dispute.dispute_json()

{payload, sig_header} = LatticeStripe.Testing.generate_webhook_payload(
  "payment_intent.succeeded",
  dispute,
  secret: "whsec_test"
)

{:ok, event} = LatticeStripe.Webhook.construct_event(payload, sig_header, "whsec_test")
assert event.type == "payment_intent.succeeded"

mandate(raw_map)

@spec mandate(map()) :: LatticeStripe.Mandate.t()

Converts a canonical Mandate fixture map into %LatticeStripe.Mandate{}.

quote(raw_map)

@spec quote(map()) :: LatticeStripe.Quote.t()

Converts a canonical Quote fixture map into %LatticeStripe.Quote{}.

setup_attempt(raw_map)

@spec setup_attempt(map()) :: LatticeStripe.SetupAttempt.t()

Converts a canonical SetupAttempt fixture map into %LatticeStripe.SetupAttempt{}.

tax_calculation(raw_map)

@spec tax_calculation(map()) :: LatticeStripe.Tax.Calculation.t()

Converts a canonical Tax Calculation fixture map into %LatticeStripe.Tax.Calculation{}.

tax_id(raw_map)

@spec tax_id(map()) :: LatticeStripe.TaxId.t()

Converts a canonical TaxId fixture map into %LatticeStripe.TaxId{}.

tax_transaction(raw_map)

@spec tax_transaction(map()) :: LatticeStripe.Tax.Transaction.t()

Converts a canonical Tax Transaction fixture map into %LatticeStripe.Tax.Transaction{}.