diff --git a/CHANGELOG.md b/CHANGELOG.md index 8587eff..efb61f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx&utm_medium=paddle-go-sdk) for information about changes to the Paddle Billing platform, the Paddle API, and other developer tools. -## 2.1.0 - 2024-10-08 +## 2.1.0 - 2024-10-15 ### Added @@ -19,6 +19,7 @@ Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx - Subscription resume union funcs now accept the required path param - `NewResumeSubscriptionRequestResumeOnASpecificDate` - `NewResumeSubscriptionRequestResumeImmediately` +- Response unmarshal for GetNotifications to the Notification struct ## 2.0.0 - 2024-09-18 diff --git a/collection.go b/collection.go index 9170f1b..9d2d19f 100644 --- a/collection.go +++ b/collection.go @@ -100,11 +100,12 @@ func (c *Collection[T]) Next(ctx context.Context) *Res[T] { return &ptrCopy } -// Ensure that Collection[T] implements the json.Unmarshaler and the +// Ensure that Collection[T] implements the response.Unmarshaler and the // client.Wanter interfaces. var ( - _ json.Unmarshaler = &Collection[any]{} - _ client.Wanter = &Collection[any]{} + _ response.UnmarshalsResponses = &Collection[any]{} + _ json.Unmarshaler = &Collection[any]{} + _ client.Wanter = &Collection[any]{} ) // Wants sets the client to be used for making requests. @@ -157,6 +158,9 @@ func (c *Collection[T]) IterErr(ctx context.Context, fn func(v T) error) error { } } +// UnmarshalsResponses acts as a marker to identify this struct must Unmarshal the entire response. +func (c *Collection[T]) UnmarshalsResponses() {} + // UnmarshalJSON unmarshals the collection from a JSON response. func (c *Collection[T]) UnmarshalJSON(b []byte) error { if len(b) == 0 { diff --git a/events.go b/events.go index 1976ec9..ffffe0c 100644 --- a/events.go +++ b/events.go @@ -22,7 +22,7 @@ type GenericEvent struct { // OccurredAt: RFC 3339 datetime string of when this event occurred. OccurredAt string `json:"occurred_at,omitempty"` // Data: New or changed entity. - Data any `json:"data,omitempty"` + Data paddlenotification.NotificationPayload `json:"data,omitempty"` } // AddressCreatedEvent represents an Event implementation for address.created event. diff --git a/example_simulations_test.go b/example_simulations_test.go new file mode 100644 index 0000000..87526db --- /dev/null +++ b/example_simulations_test.go @@ -0,0 +1,298 @@ +package paddle_test + +import ( + "context" + "fmt" + "os" + "time" + + paddle "github.com/PaddleHQ/paddle-go-sdk" + "github.com/PaddleHQ/paddle-go-sdk/pkg/paddlenotification" +) + +// Demonstrates how to create a Simulation with Payload and read the Payload back out of the response +func Example_simulation_create() { + // Create a mock HTTP server for this example - skip over this bit! + s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{simulation}}}) + + // Create a new Paddle client. + client, err := paddle.New( + os.Getenv("PADDLE_API_KEY"), + paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration. + ) + + if err != nil { + fmt.Println(err) + return + } + + ctx := context.Background() + + // Optionally set a transit ID on the context. This is useful to link your + // own request IDs to Paddle API requests. + ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1") + + simulation, err := client.CreateSimulation(ctx, paddle.NewCreateSimulationRequestSimulationSingleEventCreate(&paddle.SimulationSingleEventCreate{ + NotificationSettingID: "ntfset_01j84xydheq48n3btebwf6ndn6", + Name: "Go SDK Test with Payload", + Type: "customer.created", + Payload: paddlenotification.CustomerNotification{ + ID: "ctm_01j870snka0xdp6szgyxze6d6d", + Name: paddle.PtrTo("John Doe"), + Email: "john.doe@paddle.com", + MarketingConsent: false, + Status: paddlenotification.StatusActive, + CustomData: nil, + Locale: "en", + CreatedAt: time.Date(2024, 4, 12, 0, 0, 0, 0, time.UTC).Format(time.RFC3339), + UpdatedAt: time.Date(2024, 4, 12, 0, 0, 0, 0, time.UTC).Format(time.RFC3339), + ImportMeta: nil, + }, + })) + if err != nil { + return + } + + // Use type assertion on the response for payload + payload, ok := (simulation.Payload).(*paddlenotification.CustomerNotification) + if !ok { + fmt.Println("Payload is unexpected type") + return + } + + fmt.Println(simulation.ID) + fmt.Println(payload.ID) + // Output: + //ntfsim_01j9y0jwekrcyezscgkehvdmd6 + //ctm_01j870snka0xdp6szgyxze6d6d +} + +// Demonstrates how to update a Simulation with Payload and read the Payload back out of the response +func Example_simulation_update() { + // Create a mock HTTP server for this example - skip over this bit! + s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{simulation}}}) + + // Create a new Paddle client. + client, err := paddle.New( + os.Getenv("PADDLE_API_KEY"), + paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration. + ) + + if err != nil { + fmt.Println(err) + return + } + + ctx := context.Background() + + // Optionally set a transit ID on the context. This is useful to link your + // own request IDs to Paddle API requests. + ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1") + + simulation, err := client.UpdateSimulation( + ctx, + paddle.NewUpdateSimulationRequestSimulationSingleEventUpdate( + "ntfsim_01j9y0jwekrcyezscgkehvdmd6", + &paddle.SimulationSingleEventUpdate{ + NotificationSettingID: "ntfset_01j84xydheq48n3btebwf6ndn6", + Name: "Go SDK Test with Payload", + Type: "customer.created", + Payload: paddlenotification.CustomerNotification{ + ID: "ctm_01j870snka0xdp6szgyxze6d6d", + Name: paddle.PtrTo("John Doe"), + Email: "jane.doe@paddle.com", + MarketingConsent: false, + Status: paddlenotification.StatusActive, + CustomData: nil, + Locale: "en", + CreatedAt: time.Date(2024, 4, 12, 0, 0, 0, 0, time.UTC).Format(time.RFC3339), + UpdatedAt: time.Date(2024, 4, 12, 0, 0, 0, 0, time.UTC).Format(time.RFC3339), + ImportMeta: nil, + }, + }, + ), + ) + if err != nil { + return + } + + // Use type assertion on the response for payload + payload, ok := (simulation.Payload).(*paddlenotification.CustomerNotification) + if !ok { + fmt.Println("Payload is unexpected type") + return + } + + fmt.Println(simulation.ID) + fmt.Println(payload.ID) + // Output: + //ntfsim_01j9y0jwekrcyezscgkehvdmd6 + //ctm_01j870snka0xdp6szgyxze6d6d +} + +// // Demonstrates how to list Simulations with Payload and read the Payload back out of the response +func Example_simulation_list() { + // Create a mock HTTP server for this example - skip over this bit! + s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{simulations}}}) + + // Create a new Paddle client. + client, err := paddle.New( + os.Getenv("PADDLE_API_KEY"), + paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration. + ) + + if err != nil { + fmt.Println(err) + return + } + + ctx := context.Background() + + // Optionally set a transit ID on the context. This is useful to link your + // own request IDs to Paddle API requests. + ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1") + + simulations, err := client.ListSimulations(ctx, &paddle.ListSimulationsRequest{}) + if err != nil { + } + + simulations.Iter(ctx, func(s *paddle.Simulation) (bool, error) { + switch p := s.Payload.(type) { + case *paddlenotification.AddressNotification: + // here v could be used as concrete type AddressNotification + fmt.Println(p.CustomerID) + case *paddlenotification.CustomerNotification: + // here v could be used as concrete type CustomerNotification + fmt.Println(p.Email) + } + + return true, nil + }) + // Output: + //john.doe+blackhole@paddle.com + //ctm_01hv6y1jedq4p1n0yqn5ba3ky4 +} + +// Demonstrates how to get a Simulation with Payload and read the Payload back out of the response +func Example_simulation_get() { + // Create a mock HTTP server for this example - skip over this bit! + s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{simulation}}}) + + // Create a new Paddle client. + client, err := paddle.New( + os.Getenv("PADDLE_API_KEY"), + paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration. + ) + + if err != nil { + fmt.Println(err) + return + } + + ctx := context.Background() + + // Optionally set a transit ID on the context. This is useful to link your + // own request IDs to Paddle API requests. + ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1") + + simulation, err := client.GetSimulation(ctx, &paddle.GetSimulationRequest{SimulationID: "ntfsim_01j9y0jwekrcyezscgkehvdmd6"}) + if err != nil { + return + } + + // Use type assertion on the response for payload + payload, ok := (simulation.Payload).(*paddlenotification.CustomerNotification) + if !ok { + fmt.Println("Payload is unexpected type") + return + } + + fmt.Println(simulation.ID) + fmt.Println(payload.ID) + // Output: + //ntfsim_01j9y0jwekrcyezscgkehvdmd6 + //ctm_01j870snka0xdp6szgyxze6d6d +} + +// Demonstrates how to run a Simulation +func Example_simulation_run() { + // Create a mock HTTP server for this example - skip over this bit! + s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{simulationRun}}}) + + // Create a new Paddle client. + client, err := paddle.New( + os.Getenv("PADDLE_API_KEY"), + paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration. + ) + + if err != nil { + fmt.Println(err) + return + } + + ctx := context.Background() + + // Optionally set a transit ID on the context. This is useful to link your + // own request IDs to Paddle API requests. + ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1") + + simulationRun, err := client.CreateSimulationRun(ctx, &paddle.CreateSimulationRunRequest{ + SimulationID: "ntfsim_01j9y0jwekrcyezscgkehvdmd6", + }) + if err != nil { + return + } + + fmt.Println(simulationRun.ID) + // Output: + //ntfsimrun_01j9yq3yspewy5r8zr05vkeekd +} + +// Demonstrates how to get a SimulationRun with included SimulationRunEvents +func Example_simulation_run_get() { + // Create a mock HTTP server for this example - skip over this bit! + s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{simulationRunWithEvents}}}) + + // Create a new Paddle client. + client, err := paddle.New( + os.Getenv("PADDLE_API_KEY"), + paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration. + ) + + if err != nil { + fmt.Println(err) + return + } + + ctx := context.Background() + + // Optionally set a transit ID on the context. This is useful to link your + // own request IDs to Paddle API requests. + ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1") + + simulationRun, err := client.GetSimulationRun(ctx, &paddle.GetSimulationRunRequest{ + SimulationID: "ntfsim_01j9y0jwekrcyezscgkehvdmd6", + SimulationRunID: "ntfsimrun_01j9yq3yspewy5r8zr05vkeekd", + IncludeEvents: true, + }) + if err != nil { + return + } + + // Use type assertion on the response for payload + for _, event := range simulationRun.Events { + payload, ok := (event.Payload).(*paddlenotification.CustomerNotification) + if !ok { + fmt.Println("Payload is unexpected type") + return + } + fmt.Println(payload.ID) + fmt.Println(event.Response.StatusCode) + } + + fmt.Println(simulationRun.ID) + // Output: + //ctm_01j870snka0xdp6szgyxze6d6d + //200 + //ntfsimrun_01j9yq3yspewy5r8zr05vkeekd +} diff --git a/example_test.go b/example_test.go index 8858d6e..3a139b1 100644 --- a/example_test.go +++ b/example_test.go @@ -16,6 +16,10 @@ const ( transactionsPaginatedPg1 stubPath = "testdata/transactions_paginated_pg1.json" transactionsPaginatedPg2 stubPath = "testdata/transactions_paginated_pg2.json" priceCreatedEvent stubPath = "testdata/price_created.json" + simulation stubPath = "testdata/simulation.json" + simulations stubPath = "testdata/simulations.json" + simulationRun stubPath = "testdata/simulation_run.json" + simulationRunWithEvents stubPath = "testdata/simulation_run_with_events.json" ) //go:embed testdata diff --git a/internal/response/response.go b/internal/response/response.go index 57f433c..fff9830 100644 --- a/internal/response/response.go +++ b/internal/response/response.go @@ -13,6 +13,11 @@ import ( "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" ) +// UnmarshalsResponses is an interface implemented by type that must unmarshal responses for themselves. +type UnmarshalsResponses interface { + UnmarshalsResponses() +} + // Response is the wrapper response type returned by the Paddle API. type Response[T any] struct { Data T `json:"data"` @@ -58,7 +63,7 @@ func Handle(req *http.Request, res *http.Response, dst any) (err error) { teedBytes := bytes.NewBuffer([]byte{}) tee := io.TeeReader(res.Body, teedBytes) - if dst != nil && reflect.TypeOf(dst).Elem().Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) { + if dst != nil && reflect.TypeOf(dst).Elem().Implements(reflect.TypeOf((*UnmarshalsResponses)(nil)).Elem()) { err = json.NewDecoder(tee).Decode(dst) } else { err = json.NewDecoder(tee).Decode(r) diff --git a/pkg/paddlenotification/addresses.go b/pkg/paddlenotification/addresses.go index 4f505db..0c3f63e 100644 --- a/pkg/paddlenotification/addresses.go +++ b/pkg/paddlenotification/addresses.go @@ -25,32 +25,34 @@ type AddressUpdated struct { // AddressNotification: New or changed entity. type AddressNotification struct { + NotificationPayload `json:"-"` + // ID: Unique Paddle ID for this address entity, prefixed with `add_`. - ID string `json:"id,omitempty"` + ID string `json:"id"` // CustomerID: Paddle ID for the customer related to this address, prefixed with `cus_`. - CustomerID string `json:"customer_id,omitempty"` + CustomerID string `json:"customer_id"` // Description: Memorable description for this address. - Description *string `json:"description,omitempty"` + Description *string `json:"description"` // FirstLine: First line of this address. - FirstLine *string `json:"first_line,omitempty"` + FirstLine *string `json:"first_line"` // SecondLine: Second line of this address. - SecondLine *string `json:"second_line,omitempty"` + SecondLine *string `json:"second_line"` // City: City of this address. - City *string `json:"city,omitempty"` + City *string `json:"city"` // PostalCode: ZIP or postal code of this address. Required for some countries. - PostalCode *string `json:"postal_code,omitempty"` + PostalCode *string `json:"postal_code"` // Region: State, county, or region of this address. - Region *string `json:"region,omitempty"` + Region *string `json:"region"` // CountryCode: Supported two-letter ISO 3166-1 alpha-2 country code for this address. - CountryCode CountryCode `json:"country_code,omitempty"` + CountryCode CountryCode `json:"country_code"` // CustomData: Your own structured key-value data. - CustomData CustomData `json:"custom_data,omitempty"` + CustomData CustomData `json:"custom_data"` // Status: Whether this entity can be used in Paddle. - Status Status `json:"status,omitempty"` + Status Status `json:"status"` // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. - CreatedAt string `json:"created_at,omitempty"` + CreatedAt string `json:"created_at"` // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. - UpdatedAt string `json:"updated_at,omitempty"` + UpdatedAt string `json:"updated_at"` // ImportMeta: Import information for this entity. `null` if this entity is not imported. - ImportMeta *ImportMeta `json:"import_meta,omitempty"` + ImportMeta *ImportMeta `json:"import_meta"` } diff --git a/pkg/paddlenotification/adjustments.go b/pkg/paddlenotification/adjustments.go index 4057b04..663c025 100644 --- a/pkg/paddlenotification/adjustments.go +++ b/pkg/paddlenotification/adjustments.go @@ -145,28 +145,30 @@ type AdjustmentTaxRateUsed struct { // AdjustmentNotification: New or changed entity. type AdjustmentNotification struct { + NotificationPayload `json:"-"` + // ID: Unique Paddle ID for this adjustment entity, prefixed with `adj_`. - ID string `json:"id,omitempty"` + ID string `json:"id"` // Action: How this adjustment impacts the related transaction. - Action AdjustmentAction `json:"action,omitempty"` + Action AdjustmentAction `json:"action"` // TransactionID: Paddle ID of the transaction that this adjustment is for, prefixed with `txn_`. - TransactionID string `json:"transaction_id,omitempty"` + TransactionID string `json:"transaction_id"` /* SubscriptionID: Paddle ID for the subscription related to this adjustment, prefixed with `sub_`. Set automatically by Paddle based on the `subscription_id` of the related transaction. */ - SubscriptionID *string `json:"subscription_id,omitempty"` + SubscriptionID *string `json:"subscription_id"` /* CustomerID: Paddle ID for the customer related to this adjustment, prefixed with `ctm_`. Set automatically by Paddle based on the `customer_id` of the related transaction. */ - CustomerID string `json:"customer_id,omitempty"` + CustomerID string `json:"customer_id"` // Reason: Why this adjustment was created. Appears in the Paddle dashboard. Retained for record-keeping purposes. - Reason string `json:"reason,omitempty"` + Reason string `json:"reason"` // CreditAppliedToBalance: Whether this adjustment was applied to the related customer's credit balance. `null` unless adjustment `action` is not `credit`. - CreditAppliedToBalance *bool `json:"credit_applied_to_balance,omitempty"` + CreditAppliedToBalance *bool `json:"credit_applied_to_balance"` // CurrencyCode: Three-letter ISO 4217 currency code for this adjustment. Set automatically by Paddle based on the `currency_code` of the related transaction. - CurrencyCode CurrencyCode `json:"currency_code,omitempty"` + CurrencyCode CurrencyCode `json:"currency_code"` /* Status: Status of this adjustment. Set automatically by Paddle. @@ -174,17 +176,17 @@ type AdjustmentNotification struct { Credit adjustments don't require approval from Paddle, so they're created as `approved`. */ - Status AdjustmentStatus `json:"status,omitempty"` + Status AdjustmentStatus `json:"status"` // Items: List of items on this adjustment. - Items []AdjustmentItem `json:"items,omitempty"` + Items []AdjustmentItem `json:"items"` // Totals: Breakdown of the total for an adjustment. - Totals AdjustmentTotals `json:"totals,omitempty"` + Totals AdjustmentTotals `json:"totals"` // PayoutTotals: Breakdown of how this adjustment affects your payout balance. - PayoutTotals *PayoutTotalsAdjustment `json:"payout_totals,omitempty"` + PayoutTotals *PayoutTotalsAdjustment `json:"payout_totals"` // TaxRatesUsed: List of tax rates applied for this adjustment. - TaxRatesUsed []AdjustmentTaxRateUsed `json:"tax_rates_used,omitempty"` + TaxRatesUsed []AdjustmentTaxRateUsed `json:"tax_rates_used"` // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. - CreatedAt string `json:"created_at,omitempty"` + CreatedAt string `json:"created_at"` // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. - UpdatedAt string `json:"updated_at,omitempty"` + UpdatedAt string `json:"updated_at"` } diff --git a/pkg/paddlenotification/businesses.go b/pkg/paddlenotification/businesses.go index 0934c97..99338ce 100644 --- a/pkg/paddlenotification/businesses.go +++ b/pkg/paddlenotification/businesses.go @@ -33,26 +33,28 @@ type Contacts struct { // BusinessNotification: New or changed entity. type BusinessNotification struct { + NotificationPayload `json:"-"` + // ID: Unique Paddle ID for this business entity, prefixed with `biz_`. - ID string `json:"id,omitempty"` + ID string `json:"id"` // CustomerID: Paddle ID for the customer related to this business, prefixed with `cus_`. - CustomerID string `json:"customer_id,omitempty"` + CustomerID string `json:"customer_id"` // Name: Name of this business. - Name string `json:"name,omitempty"` + Name string `json:"name"` // CompanyNumber: Company number for this business. - CompanyNumber *string `json:"company_number,omitempty"` + CompanyNumber *string `json:"company_number"` // TaxIdentifier: Tax or VAT Number for this business. - TaxIdentifier *string `json:"tax_identifier,omitempty"` + TaxIdentifier *string `json:"tax_identifier"` // Status: Whether this entity can be used in Paddle. - Status Status `json:"status,omitempty"` + Status Status `json:"status"` // Contacts: List of contacts related to this business, typically used for sending invoices. - Contacts []Contacts `json:"contacts,omitempty"` + Contacts []Contacts `json:"contacts"` // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. - CreatedAt string `json:"created_at,omitempty"` + CreatedAt string `json:"created_at"` // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. - UpdatedAt string `json:"updated_at,omitempty"` + UpdatedAt string `json:"updated_at"` // CustomData: Your own structured key-value data. - CustomData CustomData `json:"custom_data,omitempty"` + CustomData CustomData `json:"custom_data"` // ImportMeta: Import information for this entity. `null` if this entity is not imported. - ImportMeta *ImportMeta `json:"import_meta,omitempty"` + ImportMeta *ImportMeta `json:"import_meta"` } diff --git a/pkg/paddlenotification/customers.go b/pkg/paddlenotification/customers.go index 7999f30..8e60b36 100644 --- a/pkg/paddlenotification/customers.go +++ b/pkg/paddlenotification/customers.go @@ -25,27 +25,29 @@ type CustomerUpdated struct { // CustomerNotification: New or changed entity. type CustomerNotification struct { + NotificationPayload `json:"-"` + // ID: Unique Paddle ID for this customer entity, prefixed with `ctm_`. - ID string `json:"id,omitempty"` + ID string `json:"id"` // Name: Full name of this customer. Required when creating transactions where `collection_mode` is `manual` (invoices). - Name *string `json:"name,omitempty"` + Name *string `json:"name"` // Email: Email address for this customer. - Email string `json:"email,omitempty"` + Email string `json:"email"` /* MarketingConsent: Whether this customer opted into marketing from you. `false` unless customers check the marketing consent box when using Paddle Checkout. Set automatically by Paddle. */ - MarketingConsent bool `json:"marketing_consent,omitempty"` + MarketingConsent bool `json:"marketing_consent"` // Status: Whether this entity can be used in Paddle. - Status Status `json:"status,omitempty"` + Status Status `json:"status"` // CustomData: Your own structured key-value data. - CustomData CustomData `json:"custom_data,omitempty"` + CustomData CustomData `json:"custom_data"` // Locale: Valid IETF BCP 47 short form locale tag. - Locale string `json:"locale,omitempty"` + Locale string `json:"locale"` // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. - CreatedAt string `json:"created_at,omitempty"` + CreatedAt string `json:"created_at"` // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. - UpdatedAt string `json:"updated_at,omitempty"` + UpdatedAt string `json:"updated_at"` // ImportMeta: Import information for this entity. `null` if this entity is not imported. - ImportMeta *ImportMeta `json:"import_meta,omitempty"` + ImportMeta *ImportMeta `json:"import_meta"` } diff --git a/pkg/paddlenotification/discounts.go b/pkg/paddlenotification/discounts.go index 1a86a4c..7c82497 100644 --- a/pkg/paddlenotification/discounts.go +++ b/pkg/paddlenotification/discounts.go @@ -44,50 +44,52 @@ const ( // DiscountNotification: New or changed entity. type DiscountNotification struct { + NotificationPayload `json:"-"` + // ID: Unique Paddle ID for this discount, prefixed with `dsc_`. - ID string `json:"id,omitempty"` + ID string `json:"id"` // Status: Whether this entity can be used in Paddle. `expired` and `used` are set automatically by Paddle. - Status DiscountStatus `json:"status,omitempty"` + Status DiscountStatus `json:"status"` // Description: Short description for this discount for your reference. Not shown to customers. - Description string `json:"description,omitempty"` + Description string `json:"description"` // EnabledForCheckout: Whether this discount can be redeemed by customers at checkout (`true`) or not (`false`). - EnabledForCheckout bool `json:"enabled_for_checkout,omitempty"` + EnabledForCheckout bool `json:"enabled_for_checkout"` // Code: Unique code that customers can use to redeem this discount at checkout. - Code *string `json:"code,omitempty"` + Code *string `json:"code"` // Type: Type of discount. Determines how this discount impacts the checkout or transaction total. - Type Type `json:"type,omitempty"` + Type Type `json:"type"` // Amount: Amount to discount by. For `percentage` discounts, must be an amount between `0.01` and `100`. For `flat` and `flat_per_seat` discounts, amount in the lowest denomination for a currency. - Amount string `json:"amount,omitempty"` + Amount string `json:"amount"` // CurrencyCode: Supported three-letter ISO 4217 currency code. Required where discount type is `flat` or `flat_per_seat`. - CurrencyCode *CurrencyCode `json:"currency_code,omitempty"` + CurrencyCode *CurrencyCode `json:"currency_code"` // Recur: Whether this discount applies for multiple subscription billing periods (`true`) or not (`false`). - Recur bool `json:"recur,omitempty"` + Recur bool `json:"recur"` /* MaximumRecurringIntervals: Number of subscription billing periods that this discount recurs for. Requires `recur`. `null` if this discount recurs forever. Subscription renewals, midcycle changes, and one-time charges billed to a subscription aren't considered a redemption. `times_used` is not incremented in these cases. */ - MaximumRecurringIntervals *int `json:"maximum_recurring_intervals,omitempty"` + MaximumRecurringIntervals *int `json:"maximum_recurring_intervals"` /* UsageLimit: Maximum number of times this discount can be redeemed. This is an overall limit for this discount, rather than a per-customer limit. `null` if this discount can be redeemed an unlimited amount of times. Paddle counts a usage as a redemption on a checkout, transaction, or the initial application against a subscription. Transactions created for subscription renewals, midcycle changes, and one-time charges aren't considered a redemption. */ - UsageLimit *int `json:"usage_limit,omitempty"` + UsageLimit *int `json:"usage_limit"` // RestrictTo: Product or price IDs that this discount is for. When including a product ID, all prices for that product can be discounted. `null` if this discount applies to all products and prices. - RestrictTo []string `json:"restrict_to,omitempty"` + RestrictTo []string `json:"restrict_to"` // CustomData: Your own structured key-value data. - CustomData CustomData `json:"custom_data,omitempty"` + CustomData CustomData `json:"custom_data"` // ImportMeta: Import information for this entity. `null` if this entity is not imported. - ImportMeta *ImportMeta `json:"import_meta,omitempty"` + ImportMeta *ImportMeta `json:"import_meta"` /* ExpiresAt: RFC 3339 datetime string of when this discount expires. Discount can no longer be redeemed after this date has elapsed. `null` if this discount can be redeemed forever. Expired discounts can't be redeemed against transactions or checkouts, but can be applied when updating subscriptions. */ - ExpiresAt *string `json:"expires_at,omitempty"` + ExpiresAt *string `json:"expires_at"` // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. - CreatedAt string `json:"created_at,omitempty"` + CreatedAt string `json:"created_at"` // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. - UpdatedAt string `json:"updated_at,omitempty"` + UpdatedAt string `json:"updated_at"` } diff --git a/pkg/paddlenotification/notification.go b/pkg/paddlenotification/notification.go index 852b979..b8ee672 100644 --- a/pkg/paddlenotification/notification.go +++ b/pkg/paddlenotification/notification.go @@ -4,6 +4,8 @@ package paddlenotification type NotificationEvent interface{} +type NotificationPayload interface{} + // GenericNotificationEvent: Notification payload. Includes the new or changed event. type GenericNotificationEvent struct { NotificationEvent @@ -15,7 +17,7 @@ type GenericNotificationEvent struct { // OccurredAt: RFC 3339 datetime string of when this event occurred. OccurredAt string `json:"occurred_at,omitempty"` // Data: New or changed entity. - Data any `json:"data,omitempty"` + Data NotificationPayload `json:"data,omitempty"` // NotificationID: Unique Paddle ID for this notification, prefixed with `ntf_`. NotificationID string `json:"notification_id,omitempty"` } diff --git a/pkg/paddlenotification/payouts.go b/pkg/paddlenotification/payouts.go index 85c5b13..5f95b23 100644 --- a/pkg/paddlenotification/payouts.go +++ b/pkg/paddlenotification/payouts.go @@ -18,12 +18,14 @@ type PayoutPaid struct { // PayoutNotification: New or changed entity. type PayoutNotification struct { + NotificationPayload `json:"-"` + // ID: ID for this payout. - ID string `json:"id,omitempty"` + ID string `json:"id"` // Status: Status of this payout. - Status Status `json:"status,omitempty"` + Status Status `json:"status"` // Amount: Amount paid, or scheduled to be paid, for this payout. - Amount string `json:"amount,omitempty"` + Amount string `json:"amount"` // CurrencyCode: Three-letter ISO 4217 currency code for this payout. - CurrencyCode CurrencyCodePayouts `json:"currency_code,omitempty"` + CurrencyCode CurrencyCodePayouts `json:"currency_code"` } diff --git a/pkg/paddlenotification/prices.go b/pkg/paddlenotification/prices.go index 4c72fd8..64aa888 100644 --- a/pkg/paddlenotification/prices.go +++ b/pkg/paddlenotification/prices.go @@ -25,36 +25,38 @@ type PriceUpdated struct { // PriceNotification: Represents a price entity. type PriceNotification struct { + NotificationPayload `json:"-"` + // ID: Unique Paddle ID for this price, prefixed with `pri_`. - ID string `json:"id,omitempty"` + ID string `json:"id"` // ProductID: Paddle ID for the product that this price is for, prefixed with `pro_`. - ProductID string `json:"product_id,omitempty"` + ProductID string `json:"product_id"` // Description: Internal description for this price, not shown to customers. Typically notes for your team. - Description string `json:"description,omitempty"` + Description string `json:"description"` // Type: Type of item. Standard items are considered part of your catalog and are shown on the Paddle web app. - Type *CatalogType `json:"type,omitempty"` + Type *CatalogType `json:"type"` // Name: Name of this price, shown to customers at checkout and on invoices. Typically describes how often the related product bills. - Name *string `json:"name,omitempty"` + Name *string `json:"name"` // BillingCycle: How often this price should be charged. `null` if price is non-recurring (one-time). - BillingCycle *Duration `json:"billing_cycle,omitempty"` + BillingCycle *Duration `json:"billing_cycle"` // TrialPeriod: Trial period for the product related to this price. The billing cycle begins once the trial period is over. `null` for no trial period. Requires `billing_cycle`. - TrialPeriod *Duration `json:"trial_period,omitempty"` + TrialPeriod *Duration `json:"trial_period"` // TaxMode: How tax is calculated for this price. - TaxMode TaxMode `json:"tax_mode,omitempty"` + TaxMode TaxMode `json:"tax_mode"` // UnitPrice: Base price. This price applies to all customers, except for customers located in countries where you have `unit_price_overrides`. - UnitPrice Money `json:"unit_price,omitempty"` + UnitPrice Money `json:"unit_price"` // UnitPriceOverrides: List of unit price overrides. Use to override the base price with a custom price and currency for a country or group of countries. - UnitPriceOverrides []UnitPriceOverride `json:"unit_price_overrides,omitempty"` + UnitPriceOverrides []UnitPriceOverride `json:"unit_price_overrides"` // Quantity: Limits on how many times the related product can be purchased at this price. Useful for discount campaigns. - Quantity PriceQuantity `json:"quantity,omitempty"` + Quantity PriceQuantity `json:"quantity"` // Status: Whether this entity can be used in Paddle. - Status Status `json:"status,omitempty"` + Status Status `json:"status"` // CustomData: Your own structured key-value data. - CustomData CustomData `json:"custom_data,omitempty"` + CustomData CustomData `json:"custom_data"` // ImportMeta: Import information for this entity. `null` if this entity is not imported. - ImportMeta *ImportMeta `json:"import_meta,omitempty"` + ImportMeta *ImportMeta `json:"import_meta"` // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. - CreatedAt *string `json:"created_at,omitempty"` + CreatedAt *string `json:"created_at"` // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. - UpdatedAt *string `json:"updated_at,omitempty"` + UpdatedAt *string `json:"updated_at"` } diff --git a/pkg/paddlenotification/products.go b/pkg/paddlenotification/products.go index 25c32c5..88bfc72 100644 --- a/pkg/paddlenotification/products.go +++ b/pkg/paddlenotification/products.go @@ -25,26 +25,28 @@ type ProductUpdated struct { // ProductNotification: Represents a product entity. type ProductNotification struct { + NotificationPayload `json:"-"` + // ID: Unique Paddle ID for this product, prefixed with `pro_`. - ID string `json:"id,omitempty"` + ID string `json:"id"` // Name: Name of this product. - Name string `json:"name,omitempty"` + Name string `json:"name"` // Description: Short description for this product. - Description *string `json:"description,omitempty"` + Description *string `json:"description"` // Type: Type of item. Standard items are considered part of your catalog and are shown on the Paddle web app. - Type *CatalogType `json:"type,omitempty"` + Type *CatalogType `json:"type"` // TaxCategory: Tax category for this product. Used for charging the correct rate of tax. Selected tax category must be enabled on your Paddle account. - TaxCategory TaxCategory `json:"tax_category,omitempty"` + TaxCategory TaxCategory `json:"tax_category"` // ImageURL: Image for this product. Included in the checkout and on some customer documents. - ImageURL *string `json:"image_url,omitempty"` + ImageURL *string `json:"image_url"` // CustomData: Your own structured key-value data. - CustomData CustomData `json:"custom_data,omitempty"` + CustomData CustomData `json:"custom_data"` // Status: Whether this entity can be used in Paddle. - Status Status `json:"status,omitempty"` + Status Status `json:"status"` // ImportMeta: Import information for this entity. `null` if this entity is not imported. - ImportMeta *ImportMeta `json:"import_meta,omitempty"` + ImportMeta *ImportMeta `json:"import_meta"` // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. - CreatedAt string `json:"created_at,omitempty"` + CreatedAt string `json:"created_at"` // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. - UpdatedAt *string `json:"updated_at,omitempty"` + UpdatedAt *string `json:"updated_at"` } diff --git a/pkg/paddlenotification/reports.go b/pkg/paddlenotification/reports.go index 4a794a1..744f17c 100644 --- a/pkg/paddlenotification/reports.go +++ b/pkg/paddlenotification/reports.go @@ -81,24 +81,26 @@ type ReportFilters struct { // ReportNotification: New or changed entity. type ReportNotification struct { + NotificationPayload `json:"-"` + // ID: Unique Paddle ID for this report, prefixed with `rep_` - ID string `json:"id,omitempty"` + ID string `json:"id"` /* Status: Status of this report. Set automatically by Paddle. Reports are created as `pending` initially, then move to `ready` when they're available to download. */ - Status ReportStatus `json:"status,omitempty"` + Status ReportStatus `json:"status"` // Rows: Number of records in this report. `null` if the report is `pending`. - Rows *int `json:"rows,omitempty"` + Rows *int `json:"rows"` // ExpiresAt: RFC 3339 datetime string of when this report expires. The report is no longer available to download after this date. - ExpiresAt *string `json:"expires_at,omitempty"` + ExpiresAt *string `json:"expires_at"` // UpdatedAt: RFC 3339 datetime string of when this report was last updated. - UpdatedAt string `json:"updated_at,omitempty"` + UpdatedAt string `json:"updated_at"` // CreatedAt: RFC 3339 datetime string of when this report was created. - CreatedAt string `json:"created_at,omitempty"` + CreatedAt string `json:"created_at"` // Type: Type of report to create. - Type ReportType `json:"type,omitempty"` + Type ReportType `json:"type"` // Filters: Filter criteria for this report. If omitted when creating, reports are filtered to include data updated in the last 30 days. This means `updated_at` is greater than or equal to (`gte`) the date 30 days ago from the time the report was generated. - Filters []ReportFilters `json:"filters,omitempty"` + Filters []ReportFilters `json:"filters"` } diff --git a/pkg/paddlenotification/subscriptions.go b/pkg/paddlenotification/subscriptions.go index 2de698e..5371938 100644 --- a/pkg/paddlenotification/subscriptions.go +++ b/pkg/paddlenotification/subscriptions.go @@ -140,54 +140,58 @@ type SubscriptionItem struct { // SubscriptionNotification: New or changed entity. type SubscriptionNotification struct { + NotificationPayload `json:"-"` + // ID: Unique Paddle ID for this subscription entity, prefixed with `sub_`. - ID string `json:"id,omitempty"` + ID string `json:"id"` // Status: Status of this subscription. Set automatically by Paddle. Use the pause subscription or cancel subscription operations to change. - Status SubscriptionStatus `json:"status,omitempty"` + Status SubscriptionStatus `json:"status"` // CustomerID: Paddle ID of the customer that this subscription is for, prefixed with `ctm_`. - CustomerID string `json:"customer_id,omitempty"` + CustomerID string `json:"customer_id"` // AddressID: Paddle ID of the address that this subscription is for, prefixed with `add_`. - AddressID string `json:"address_id,omitempty"` + AddressID string `json:"address_id"` // BusinessID: Paddle ID of the business that this subscription is for, prefixed with `biz_`. - BusinessID *string `json:"business_id,omitempty"` + BusinessID *string `json:"business_id"` // CurrencyCode: Supported three-letter ISO 4217 currency code. Transactions for this subscription are created in this currency. Must be `USD`, `EUR`, or `GBP` if `collection_mode` is `manual`. - CurrencyCode CurrencyCode `json:"currency_code,omitempty"` + CurrencyCode CurrencyCode `json:"currency_code"` // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. - CreatedAt string `json:"created_at,omitempty"` + CreatedAt string `json:"created_at"` // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. - UpdatedAt string `json:"updated_at,omitempty"` + UpdatedAt string `json:"updated_at"` // StartedAt: RFC 3339 datetime string of when this subscription started. This may be different from `first_billed_at` if the subscription started in trial. - StartedAt *string `json:"started_at,omitempty"` + StartedAt *string `json:"started_at"` // FirstBilledAt: RFC 3339 datetime string of when this subscription was first billed. This may be different from `started_at` if the subscription started in trial. - FirstBilledAt *string `json:"first_billed_at,omitempty"` + FirstBilledAt *string `json:"first_billed_at"` // NextBilledAt: RFC 3339 datetime string of when this subscription is next scheduled to be billed. - NextBilledAt *string `json:"next_billed_at,omitempty"` + NextBilledAt *string `json:"next_billed_at"` // PausedAt: RFC 3339 datetime string of when this subscription was paused. Set automatically by Paddle when the pause subscription operation is used. `null` if not paused. - PausedAt *string `json:"paused_at,omitempty"` + PausedAt *string `json:"paused_at"` // CanceledAt: RFC 3339 datetime string of when this subscription was canceled. Set automatically by Paddle when the cancel subscription operation is used. `null` if not canceled. - CanceledAt *string `json:"canceled_at,omitempty"` + CanceledAt *string `json:"canceled_at"` // Discount: Details of the discount applied to this subscription. - Discount *SubscriptionDiscountTimePeriod `json:"discount,omitempty"` + Discount *SubscriptionDiscountTimePeriod `json:"discount"` // CollectionMode: How payment is collected for transactions created for this subscription. `automatic` for checkout, `manual` for invoices. - CollectionMode CollectionMode `json:"collection_mode,omitempty"` + CollectionMode CollectionMode `json:"collection_mode"` // BillingDetails: Details for invoicing. Required if `collection_mode` is `manual`. - BillingDetails *BillingDetails `json:"billing_details,omitempty"` + BillingDetails *BillingDetails `json:"billing_details"` // CurrentBillingPeriod: Current billing period for this subscription. Set automatically by Paddle based on the billing cycle. `null` for `paused` and `canceled` subscriptions. - CurrentBillingPeriod *TimePeriod `json:"current_billing_period,omitempty"` + CurrentBillingPeriod *TimePeriod `json:"current_billing_period"` // BillingCycle: How often this subscription renews. Set automatically by Paddle based on the prices on this subscription. - BillingCycle Duration `json:"billing_cycle,omitempty"` + BillingCycle Duration `json:"billing_cycle"` // ScheduledChange: Change that's scheduled to be applied to a subscription. Use the pause subscription, cancel subscription, and resume subscription operations to create scheduled changes. `null` if no scheduled changes. - ScheduledChange *SubscriptionScheduledChange `json:"scheduled_change,omitempty"` + ScheduledChange *SubscriptionScheduledChange `json:"scheduled_change"` // Items: Represents a subscription item. - Items []SubscriptionItem `json:"items,omitempty"` + Items []SubscriptionItem `json:"items"` // CustomData: Your own structured key-value data. - CustomData CustomData `json:"custom_data,omitempty"` + CustomData CustomData `json:"custom_data"` // ImportMeta: Import information for this entity. `null` if this entity is not imported. - ImportMeta *ImportMeta `json:"import_meta,omitempty"` + ImportMeta *ImportMeta `json:"import_meta"` } // SubscriptionCreatedNotification: New or changed entity. type SubscriptionCreatedNotification struct { + NotificationPayload `json:"-"` + // ID: Unique Paddle ID for this subscription entity, prefixed with `sub_`. ID string `json:"id,omitempty"` // TransactionID: Paddle ID for the transaction entity that resulted in this subscription being created, prefixed with `txn_`. diff --git a/pkg/paddlenotification/transactions.go b/pkg/paddlenotification/transactions.go index 885300f..8dbb2fb 100644 --- a/pkg/paddlenotification/transactions.go +++ b/pkg/paddlenotification/transactions.go @@ -384,48 +384,50 @@ type Checkout struct { // TransactionNotification: New or changed entity. type TransactionNotification struct { + NotificationPayload `json:"-"` + // ID: Unique Paddle ID for this transaction entity, prefixed with `txn_`. - ID string `json:"id,omitempty"` + ID string `json:"id"` // Status: Status of this transaction. You may set a transaction to `billed` or `canceled`, other statuses are set automatically by Paddle. Automatically-collected transactions may return `completed` if payment is captured successfully, or `past_due` if payment failed. - Status TransactionStatus `json:"status,omitempty"` + Status TransactionStatus `json:"status"` // CustomerID: Paddle ID of the customer that this transaction is for, prefixed with `ctm_`. - CustomerID *string `json:"customer_id,omitempty"` + CustomerID *string `json:"customer_id"` // AddressID: Paddle ID of the address that this transaction is for, prefixed with `add_`. - AddressID *string `json:"address_id,omitempty"` + AddressID *string `json:"address_id"` // BusinessID: Paddle ID of the business that this transaction is for, prefixed with `biz_`. - BusinessID *string `json:"business_id,omitempty"` + BusinessID *string `json:"business_id"` // CustomData: Your own structured key-value data. - CustomData CustomData `json:"custom_data,omitempty"` + CustomData CustomData `json:"custom_data"` // CurrencyCode: Supported three-letter ISO 4217 currency code. Must be `USD`, `EUR`, or `GBP` if `collection_mode` is `manual`. - CurrencyCode CurrencyCode `json:"currency_code,omitempty"` + CurrencyCode CurrencyCode `json:"currency_code"` // Origin: Describes how this transaction was created. - Origin TransactionOrigin `json:"origin,omitempty"` + Origin TransactionOrigin `json:"origin"` // SubscriptionID: Paddle ID of the subscription that this transaction is for, prefixed with `sub_`. - SubscriptionID *string `json:"subscription_id,omitempty"` + SubscriptionID *string `json:"subscription_id"` // InvoiceID: Paddle ID of the invoice that this transaction is related to, prefixed with `inv_`. Used for compatibility with the Paddle Invoice API, which is now deprecated. This field is scheduled to be removed in the next version of the Paddle API. - InvoiceID *string `json:"invoice_id,omitempty"` + InvoiceID *string `json:"invoice_id"` // InvoiceNumber: Invoice number for this transaction. Automatically generated by Paddle when you mark a transaction as `billed` where `collection_mode` is `manual`. - InvoiceNumber *string `json:"invoice_number,omitempty"` + InvoiceNumber *string `json:"invoice_number"` // CollectionMode: How payment is collected for this transaction. `automatic` for checkout, `manual` for invoices. - CollectionMode CollectionMode `json:"collection_mode,omitempty"` + CollectionMode CollectionMode `json:"collection_mode"` // DiscountID: Paddle ID of the discount applied to this transaction, prefixed with `dsc_`. - DiscountID *string `json:"discount_id,omitempty"` + DiscountID *string `json:"discount_id"` // BillingDetails: Details for invoicing. Required if `collection_mode` is `manual`. - BillingDetails *BillingDetails `json:"billing_details,omitempty"` + BillingDetails *BillingDetails `json:"billing_details"` // BillingPeriod: Time period that this transaction is for. Set automatically by Paddle for subscription renewals to describe the period that charges are for. - BillingPeriod *TimePeriod `json:"billing_period,omitempty"` + BillingPeriod *TimePeriod `json:"billing_period"` // Items: List of items on this transaction. For calculated totals, use `details.line_items`. - Items []TransactionItem `json:"items,omitempty"` + Items []TransactionItem `json:"items"` // Details: Calculated totals for a transaction, including proration, discounts, tax, and currency conversion. Considered the source of truth for totals on a transaction. - Details TransactionDetails `json:"details,omitempty"` + Details TransactionDetails `json:"details"` // Payments: List of payment attempts for this transaction, including successful payments. Sorted by `created_at` in descending order, so most recent attempts are returned first. - Payments []TransactionPaymentAttempt `json:"payments,omitempty"` + Payments []TransactionPaymentAttempt `json:"payments"` // Checkout: Paddle Checkout details for this transaction. Returned for automatically-collected transactions and where `billing_details.enable_checkout` is `true` for manually-collected transactions; `null` otherwise. - Checkout *Checkout `json:"checkout,omitempty"` + Checkout *Checkout `json:"checkout"` // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. - CreatedAt string `json:"created_at,omitempty"` + CreatedAt string `json:"created_at"` // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. - UpdatedAt string `json:"updated_at,omitempty"` + UpdatedAt string `json:"updated_at"` // BilledAt: RFC 3339 datetime string of when this transaction was marked as `billed`. `null` for transactions that are not `billed` or `completed`. Set automatically by Paddle. - BilledAt *string `json:"billed_at,omitempty"` + BilledAt *string `json:"billed_at"` } diff --git a/shared.go b/shared.go index 0af7b83..134b0b4 100644 --- a/shared.go +++ b/shared.go @@ -2,7 +2,13 @@ package paddle -import paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +import ( + "encoding/json" + "strings" + + paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" + "github.com/PaddleHQ/paddle-go-sdk/pkg/paddlenotification" +) // ErrNotFound represents a `not_found` error. // See https://developer.paddle.com/errors/shared/not_found for more information. @@ -1485,9 +1491,6 @@ const ( SimulationTypeNameSubscriptionCancellation SimulationTypeName = "subscription_cancellation" ) -// Payload: Simulation payload. `null` for scenarios. -type Payload map[string]any - // SimulationEventStatus: Status of this simulation run log.. type SimulationEventStatus string @@ -1524,7 +1527,7 @@ type SimulationEvent struct { // EventType: Type of event sent by Paddle, in the format `entity.event_type`. EventType EventTypeName `json:"event_type,omitempty"` // Payload: Simulation payload. Pass a JSON object that matches the schema for an event type to simulate a custom payload. If omitted, Paddle populates with a demo example. - Payload Payload `json:"payload,omitempty"` + Payload paddlenotification.NotificationPayload `json:"payload,omitempty"` // Request: Information about the request. Sent by Paddle as part of the simulation. Request *SimulationEventRequest `json:"request,omitempty"` // Response: Information about the response. Sent by the responding server for the notification setting. @@ -1534,3 +1537,52 @@ type SimulationEvent struct { // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. UpdatedAt string `json:"updated_at,omitempty"` } + +// UnmarshalJSON implements the json.Unmarshaler interface for Notification +func (n *SimulationEvent) UnmarshalJSON(data []byte) error { + type alias SimulationEvent + if err := json.Unmarshal(data, (*alias)(n)); err != nil { + return err + } + + var t paddlenotification.NotificationPayload + switch strings.Split(string(n.EventType), ".")[0] { + case "address": + t = &paddlenotification.AddressNotification{} + case "adjustment": + t = &paddlenotification.AdjustmentNotification{} + case "business": + t = &paddlenotification.BusinessNotification{} + case "customer": + t = &paddlenotification.CustomerNotification{} + case "discount": + t = &paddlenotification.DiscountNotification{} + case "payout": + t = &paddlenotification.PayoutNotification{} + case "price": + t = &paddlenotification.PriceNotification{} + case "product": + t = &paddlenotification.ProductNotification{} + case "report": + t = &paddlenotification.ReportNotification{} + case "subscription": + t = &paddlenotification.SubscriptionNotification{} + case "transaction": + t = &paddlenotification.TransactionNotification{} + default: + t = map[string]any{} + } + + rawT, err := json.Marshal(n.Payload) + if err != nil { + return err + } + + if err := json.Unmarshal(rawT, t); err != nil { + return err + } + + n.Payload = t + + return nil +} diff --git a/simulations.go b/simulations.go index fd1a74f..ede0d11 100644 --- a/simulations.go +++ b/simulations.go @@ -5,6 +5,9 @@ package paddle import ( "context" "encoding/json" + "strings" + + "github.com/PaddleHQ/paddle-go-sdk/pkg/paddlenotification" ) // Simulation: Represents a simulation entity. @@ -20,7 +23,7 @@ type Simulation struct { // Type: Single event sent for this simulation, in the format `entity.event_type`. Type SimulationTypeName `json:"type,omitempty"` // Payload: Simulation payload. `null` for scenarios. - Payload *Payload `json:"payload,omitempty"` + Payload paddlenotification.NotificationPayload `json:"payload,omitempty"` // LastRunAt: RFC 3339 datetime string of when this simulation was last run. `null` until run. Set automatically by Paddle. LastRunAt *string `json:"last_run_at,omitempty"` // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. @@ -29,6 +32,60 @@ type Simulation struct { UpdatedAt string `json:"updated_at,omitempty"` } +// UnmarshalJSON implements the json.Unmarshaler interface for Notification +func (s *Simulation) UnmarshalJSON(data []byte) error { + type alias Simulation + + if err := json.Unmarshal(data, (*alias)(s)); err != nil { + return err + } + + if s.Payload == nil || !strings.Contains(string(s.Type), ".") { + return nil + } + + var t paddlenotification.NotificationPayload + switch strings.Split(string(s.Type), ".")[0] { + case "address": + t = &paddlenotification.AddressNotification{} + case "adjustment": + t = &paddlenotification.AdjustmentNotification{} + case "business": + t = &paddlenotification.BusinessNotification{} + case "customer": + t = &paddlenotification.CustomerNotification{} + case "discount": + t = &paddlenotification.DiscountNotification{} + case "payout": + t = &paddlenotification.PayoutNotification{} + case "price": + t = &paddlenotification.PriceNotification{} + case "product": + t = &paddlenotification.ProductNotification{} + case "report": + t = &paddlenotification.ReportNotification{} + case "subscription": + t = &paddlenotification.SubscriptionNotification{} + case "transaction": + t = &paddlenotification.TransactionNotification{} + default: + t = map[string]any{} + } + + rawT, err := json.Marshal(s.Payload) + if err != nil { + return err + } + + if err := json.Unmarshal(rawT, t); err != nil { + return err + } + + s.Payload = t + + return nil +} + // SimulationSingleEventCreate: Single event simulations play a single event. type SimulationSingleEventCreate struct { // NotificationSettingID: Paddle ID of the notification setting where this simulation is sent, prefixed with `ntfset_`. @@ -38,7 +95,7 @@ type SimulationSingleEventCreate struct { // Type: Single event sent for this simulation, in the format `entity.event_type`. Type EventTypeName `json:"type,omitempty"` // Payload: Simulation payload. Pass a JSON object that matches the schema for an event type to simulate a custom payload. If omitted, Paddle populates with a demo example. - Payload *Payload `json:"payload,omitempty"` + Payload paddlenotification.NotificationPayload `json:"payload,omitempty"` } // SimulationScenarioType: Scenario for this simulation. Scenario simulations play all events sent for a subscription lifecycle event.. @@ -73,7 +130,7 @@ type SimulationSingleEventUpdate struct { // Type: Single event sent for this simulation, in the format `entity.event_type`. Type EventTypeName `json:"type,omitempty"` // Payload: Simulation payload. Pass a JSON object that matches the schema for an event type to simulate a custom payload. Set to `null` to clear and populate with a demo example. - Payload *Payload `json:"payload,omitempty"` + Payload paddlenotification.NotificationPayload `json:"payload,omitempty"` } // SimulationScenarioUpdate: Scenario simulations play all events sent for a subscription lifecycle event. diff --git a/testdata/simulation.json b/testdata/simulation.json new file mode 100644 index 0000000..d66b3bd --- /dev/null +++ b/testdata/simulation.json @@ -0,0 +1,27 @@ +{ + "data": { + "id": "ntfsim_01j9y0jwekrcyezscgkehvdmd6", + "notification_setting_id": "ntfset_01j84xydheq48n3btebwf6ndn6", + "name": "Go SDK Test with Payload", + "type": "customer.created", + "status": "active", + "payload": { + "id": "ctm_01j870snka0xdp6szgyxze6d6d", + "name": "John Doe", + "email": "john.doe@paddle.com", + "marketing_consent": false, + "status": "active", + "custom_data": null, + "locale": "en", + "created_at": "2024-04-12T00:00:00Z", + "updated_at": "2024-04-12T00:00:00Z", + "import_meta": null + }, + "last_run_at": null, + "created_at": "2024-10-11T14:44:06.739792Z", + "updated_at": "2024-10-11T14:44:06.739792Z" + }, + "meta": { + "request_id": "cba8e79a-e5af-463f-9648-338262c4fd00" + } +} diff --git a/testdata/simulation_run.json b/testdata/simulation_run.json new file mode 100644 index 0000000..f902a8c --- /dev/null +++ b/testdata/simulation_run.json @@ -0,0 +1,12 @@ +{ + "data" : { + "id" : "ntfsimrun_01j9yq3yspewy5r8zr05vkeekd", + "status" : "pending", + "type" : "customer.created", + "created_at" : "2024-10-11T21:17:54.870633Z", + "updated_at" : "2024-10-11T21:17:54.870633Z" + }, + "meta" : { + "request_id" : "4779ce0c-4830-4ab1-b3f2-86cd3bce671d" + } +} diff --git a/testdata/simulation_run_with_events.json b/testdata/simulation_run_with_events.json new file mode 100644 index 0000000..1889555 --- /dev/null +++ b/testdata/simulation_run_with_events.json @@ -0,0 +1,38 @@ +{ + "data" : { + "id" : "ntfsimrun_01j9yq3yspewy5r8zr05vkeekd", + "status" : "completed", + "type" : "customer.created", + "created_at" : "2024-10-11T21:17:54.870633Z", + "updated_at" : "2024-10-11T21:17:55.460363Z", + "events" : [ { + "id" : "ntfsimevt_01j9yq3yt7b418zwfs5pzsefqs", + "status" : "success", + "event_type" : "customer.created", + "payload" : { + "id" : "ctm_01j870snka0xdp6szgyxze6d6d", + "name" : "John Doe", + "email" : "john.doe+blackhole@paddle.com", + "locale" : "en", + "status" : "active", + "created_at" : "2024-04-12T00:00:00Z", + "updated_at" : "2024-04-12T00:00:00Z", + "custom_data" : null, + "import_meta" : null, + "marketing_consent" : false + }, + "request" : { + "body" : "{\"event_id\":\"ntfsimevt_01j9yq3yt7b418zwfs5pzsefqs\",\"event_type\":\"customer.created\",\"occurred_at\":\"2024-10-11T21:17:54.887463Z\",\"notification_id\":\"ntfsimntf_01j9yq3yz7y77gszvatscnqfan\",\"data\":{\"id\":\"ctm_01j870snka0xdp6szgyxze6d6d\",\"name\":\"John Doe\",\"email\":\"john.doe+blackhole@paddle.com\",\"locale\":\"en\",\"status\":\"active\",\"created_at\":\"2024-04-12T00:00:00Z\",\"updated_at\":\"2024-04-12T00:00:00Z\",\"custom_data\":null,\"import_meta\":null,\"marketing_consent\":false}}" + }, + "response" : { + "body" : "{\"status\":\"SUCCESS\",\"message\":\"Request handled by Hookdeck. Check your dashboard to inspect the request: https://dashboard.hookdeck.com/requests/123\",\"request_id\":\"123\"}", + "status_code" : 200 + }, + "created_at" : "2024-10-11T21:17:54.887463Z", + "updated_at" : "2024-10-11T21:17:55.427851Z" + } ] + }, + "meta" : { + "request_id" : "252fb511-bf6e-45bf-b99e-6f6a9533183d" + } +} diff --git a/testdata/simulations.json b/testdata/simulations.json new file mode 100644 index 0000000..8519ce0 --- /dev/null +++ b/testdata/simulations.json @@ -0,0 +1,61 @@ +{ + "data": [ + { + "id": "ntfsim_01j9y0jwekrcyezscgkehvdmd6", + "notification_setting_id": "ntfset_01j84xydheq48n3btebwf6ndn6", + "name": "Go SDK", + "type": "customer.created", + "status": "active", + "payload": { + "id": "ctm_01j870snka0xdp6szgyxze6d6d", + "name": "John Doe", + "email": "john.doe+blackhole@paddle.com", + "locale": "en", + "status": "active", + "created_at": "2024-04-12T00:00:00Z", + "updated_at": "2024-04-12T00:00:00Z", + "custom_data": null, + "import_meta": null, + "marketing_consent": false + }, + "last_run_at": null, + "created_at": "2024-10-11T14:44:06.739792Z", + "updated_at": "2024-10-11T14:44:06.739792Z" + }, + { + "id": "ntfsim_01j9y07xtgjg4nycye6wyp4rb8", + "notification_setting_id": "ntfset_01j84xydheq48n3btebwf6ndn6", + "name": "Go SDK", + "type": "address.created", + "status": "active", + "payload": { + "id": "add_01hv8gq3318ktkfengj2r75gfx", + "city": "New York", + "region": "NY", + "status": "active", + "created_at": "2024-04-12T06:42:58.785Z", + "first_line": "4050 Jefferson Plaza, 41st Floor", + "updated_at": "2024-04-12T06:42:58.785Z", + "custom_data": null, + "customer_id": "ctm_01hv6y1jedq4p1n0yqn5ba3ky4", + "description": "Head Office", + "import_meta": null, + "postal_code": "10021", + "second_line": null, + "country_code": "US" + }, + "last_run_at": null, + "created_at": "2024-10-11T14:38:07.696081Z", + "updated_at": "2024-10-11T14:38:07.696081Z" + } + ], + "meta": { + "pagination": { + "per_page": 50, + "estimated_total": 2, + "next": "https://api.paddle.com/simulations?after=ntfsim_01j9y07xtgjg4nycye6wyp4rb8", + "has_more": false + }, + "request_id": "37c14546-8d1e-4824-8572-ab4b12b9e08a" + } +}