# `Accrue.Billing.Metadata`
[🔗](https://github.com/szTheory/accrue/blob/accrue-v0.3.0/lib/accrue/billing/metadata.ex#L1)

Stripe-compatible metadata validation and merge helpers.

Metadata in Accrue follows the exact Stripe metadata contract:

  - Flat `%{String.t() => String.t()}` map (no nested maps)
  - Maximum 50 keys
  - Key length: max 40 characters
  - Value length: max 500 characters
  - `""` or `nil` value means "delete this key" on update

## Merge semantics

`shallow_merge/2` merges new keys into existing metadata. Keys whose
value is `""` or `nil` are removed from the result. No deep merge is
supported (D2-10).

# `shallow_merge`

```elixir
@spec shallow_merge(map(), map()) :: map()
```

Shallow-merges `new_metadata` into `existing_metadata`.

Keys with `""` or `nil` values in `new_metadata` are deleted from
the result. All other keys are set or overwritten.

## Examples

    iex> Accrue.Billing.Metadata.shallow_merge(%{"a" => "1"}, %{"b" => "2"})
    %{"a" => "1", "b" => "2"}

    iex> Accrue.Billing.Metadata.shallow_merge(%{"a" => "1", "b" => "2"}, %{"a" => ""})
    %{"b" => "2"}

    iex> Accrue.Billing.Metadata.shallow_merge(%{"a" => "1"}, %{"a" => nil})
    %{}

# `validate_metadata`

```elixir
@spec validate_metadata(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
```

Validates a metadata field on the given changeset.

Checks that the value is a flat string-key/string-value map within
Stripe-compatible constraints. Returns the changeset with errors added
if validation fails.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
