Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions docs/specs/v2-developer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13762,6 +13762,7 @@ components:
- default_locale
- components_localizations
- automatically_scale_font_size
- state_declarations
properties:
revision:
description: The revision number of this version.
Expand Down Expand Up @@ -13806,6 +13807,20 @@ components:
type: boolean
default: true
example: true
state_declarations:
description: >-
State declarations for this version, keyed by state key name. An
empty object when the paywall has no state declarations; null for
paywalls not written to since before state declarations were
introduced.
type: object
nullable: true
additionalProperties:
$ref: '#/components/schemas/StateDeclaration'
example:
selectedPackage:
type: string
default: monthly
fonts:
description: >-
Font objects used by the published paywall components. Only present
Expand Down Expand Up @@ -15014,6 +15029,94 @@ components:
maxLength: 30
additionalProperties: false
additionalProperties: false
StateDeclaration:
description: >-
Declaration of a single paywall state key, giving its value type and a
default value matching that type.
oneOf:
- $ref: '#/components/schemas/StateDeclarationBooleanVariant'
- $ref: '#/components/schemas/StateDeclarationIntegerVariant'
- $ref: '#/components/schemas/StateDeclarationDoubleVariant'
- $ref: '#/components/schemas/StateDeclarationStringVariant'
discriminator:
propertyName: type
mapping:
boolean: '#/components/schemas/StateDeclarationBooleanVariant'
integer: '#/components/schemas/StateDeclarationIntegerVariant'
double: '#/components/schemas/StateDeclarationDoubleVariant'
string: '#/components/schemas/StateDeclarationStringVariant'
StateDeclarationBooleanVariant:
description: Declaration of a boolean-valued paywall state key.
type: object
required:
- type
- default
properties:
type:
description: The value type of the state key.
type: string
enum:
- boolean
example: boolean
default:
description: The initial value for the state key.
type: boolean
example: true
additionalProperties: true
StateDeclarationDoubleVariant:
description: Declaration of a double-valued paywall state key.
type: object
required:
- type
- default
properties:
type:
description: The value type of the state key.
type: string
enum:
- double
example: double
default:
description: The initial value for the state key.
type: number
example: 0.25
additionalProperties: true
StateDeclarationIntegerVariant:
description: Declaration of a integer-valued paywall state key.
type: object
required:
- type
- default
properties:
type:
description: The value type of the state key.
type: string
enum:
- integer
example: integer
default:
description: The initial value for the state key.
type: integer
example: 3
additionalProperties: true
StateDeclarationStringVariant:
description: Declaration of a string-valued paywall state key.
type: object
required:
- type
- default
properties:
type:
description: The value type of the state key.
type: string
enum:
- string
example: string
default:
description: The initial value for the state key.
type: string
example: monthly
additionalProperties: true
StoreKitConfigFile:
type: object
description: Contents of a generated StoreKit config file for an app
Expand Down
8 changes: 6 additions & 2 deletions internal/api/paywalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type PaywallComponentsVersion struct {
Revision *int `json:"revision"`
ComponentsConfig json.RawMessage `json:"components_config"`
ComponentsLocalizations json.RawMessage `json:"components_localizations"`
StateDeclarations json.RawMessage `json:"state_declarations,omitempty"`
DefaultLocale string `json:"default_locale"`
AutomaticallyScaleFontSize bool `json:"automatically_scale_font_size"`
}
Expand All @@ -41,8 +42,11 @@ type PaywallDraftUpdate struct {
Revision int `json:"revision"`
ComponentsConfig json.RawMessage `json:"components_config"`
ComponentsLocalizations json.RawMessage `json:"components_localizations"`
DefaultLocale string `json:"default_locale"`
Name *string `json:"name,omitempty"`
// Unset must marshal as an omitted field, never as null: the server keeps
// stored declarations when the field is absent but clears them on explicit null.
StateDeclarations json.RawMessage `json:"state_declarations,omitempty"`
DefaultLocale string `json:"default_locale"`
Name *string `json:"name,omitempty"`
}

func (s *PaywallsService) List(ctx context.Context, projectID string) (*Page[Paywall], error) {
Expand Down
28 changes: 28 additions & 0 deletions internal/api/paywalls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package api_test

import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
Expand All @@ -11,6 +12,33 @@ import (
"github.com/revenuecat/cli/internal/api"
)

// An explicit "state_declarations": null clears the stored declarations
// server-side, so an unset field must be omitted from the PATCH entirely.
func TestPaywallDraftUpdateOmitsUnsetStateDeclarations(t *testing.T) {
update := api.PaywallDraftUpdate{
Revision: 1,
ComponentsConfig: json.RawMessage(`{}`),
ComponentsLocalizations: json.RawMessage(`{}`),
DefaultLocale: "en_US",
}
unset, err := json.Marshal(update)
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(unset), "state_declarations") {
t.Fatalf("unset state_declarations must be omitted, not sent as null: %s", unset)
}

update.StateDeclarations = json.RawMessage(`{}`)
set, err := json.Marshal(update)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(set), `"state_declarations":{}`) {
t.Fatalf("set state_declarations missing from PATCH body: %s", set)
}
}

func TestPaywallsPublishPreservesPublishedState(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || r.URL.Path != "/projects/proj/paywalls/pw/actions/publish" {
Expand Down
Loading
Loading