Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for all checkout creation options #18

Merged
Merged
Show file tree
Hide file tree
Changes from 3 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
64 changes: 54 additions & 10 deletions checkout.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,58 @@ type CheckoutApiResponse = ApiResponse[CheckoutAttributes, ApiResponseRelationsh
// CheckoutsApiResponse is the api response for a list of checkout.
type CheckoutsApiResponse = ApiResponseList[CheckoutAttributes, ApiResponseRelationshipsDiscount]

// CheckoutCreateParams are parameters for creating a checkout
type CheckoutCreateParams struct {
CustomPrice int `json:"custom_price"`
EnabledVariants []int `json:"enabled_variants"`
ButtonColor string `json:"button_color"`
DiscountCode *string `json:"discount_code"`
CustomData map[string]string `json:"custom_data"`
ExpiresAt time.Time `json:"expires_at"`
StoreID string `json:"store_id"`
VariantID string `json:"variant_id"`
// Quantities represents variant quantities when creating checkout
type Quantities struct {
Copy link
Member

Choose a reason for hiding this comment

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

Let's use the same structure of variable names so this struct will be

CheckoutCreateDataVariantQuantity instead of Quantities

VariantId int `json:"variant_id"`
Quantity int `json:"quantity"`
}

// CheckoutCreateData represents the data options for creating a checkout.
type CheckoutCreateData struct {
Email string `json:"email,omitempty"`
Name string `json:"name,omitempty"`
BillingAddressCountry string `json:"billing_address.country,omitempty"`
BillingAddressZip string `json:"billing_address.zip,omitempty"`
TaxNumber string `json:"tax_number,omitempty"`
DiscountCode string `json:"discount_code,omitempty"`
Custom map[string]any `json:"custom,omitempty"`
VariantQuantities []Quantities `json:"variant_quantities,omitempty"`
}

// CheckoutCreateOptions represents the checkout options for creating a checkout.
//
// Note: We use pointers for the boolean fields as otherwise, setting them to "false" would omit them, which would
// break some of the boolean checks in the API. See: https://docs.lemonsqueezy.com/api/checkouts#create-a-checkout
type CheckoutCreateOptions struct {
Embed *bool `json:"embed,omitempty"`
Media *bool `json:"media,omitempty"`
Logo *bool `json:"logo,omitempty"`
Desc *bool `json:"desc,omitempty"`
Discount *bool `json:"discount,omitempty"`
Dark *bool `json:"dark,omitempty"`
SubscriptionPreview *bool `json:"subscription_preview,omitempty"`
ButtonColor string `json:"button_color,omitempty"`
}

// CheckoutCreateProductOptions represents product options for creating a checkout.
type CheckoutCreateProductOptions struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Media []string `json:"media,omitempty"`
RedirectUrl string `json:"redirect_url,omitempty"`
ReceiptButtonText string `json:"receipt_button_text,omitempty"`
ReceiptLinkUrl string `json:"receipt_link_url,omitempty"`
ReceiptThankYouNote string `json:"receipt_thank_you_note,omitempty"`
EnabledVariants []int `json:"enabled_variants,omitempty"`
}

// CheckoutCreateAttributes represents individual parameters for creating a checkout.
type CheckoutCreateAttributes struct {
CustomPrice *int `json:"custom_price,omitempty"`
ProductOptions CheckoutCreateProductOptions `json:"product_options,omitempty"`
CheckoutOptions CheckoutCreateOptions `json:"checkout_options,omitempty"`
CheckoutData CheckoutCreateData `json:"checkout_data,omitempty"`
Preview *bool `json:"preview,omitempty"`
TestMode *bool `json:"test_mode,omitempty"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
}
30 changes: 6 additions & 24 deletions checkouts_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"context"
"encoding/json"
"net/http"
"time"
"strconv"
)

// CheckoutsService is the API client for the `/v1/checkouts` endpoint
Expand All @@ -13,39 +13,21 @@ type CheckoutsService service
// Create a custom checkout.
//
// https://docs.lemonsqueezy.com/api/checkouts#create-a-checkout
func (service *CheckoutsService) Create(ctx context.Context, params *CheckoutCreateParams) (*CheckoutApiResponse, *Response, error) {
checkoutData := map[string]any{
"custom": params.CustomData,
}
if params.DiscountCode != nil {
checkoutData["discount_code"] = params.DiscountCode
}

func (service *CheckoutsService) Create(ctx context.Context, variantId int, storeId int, attributes *CheckoutCreateAttributes) (*CheckoutApiResponse, *Response, error) {
Copy link
Member

Choose a reason for hiding this comment

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

I see you used int for variantId and storeId instead of string I like the idea of int but this will make the library inconsistent, if only one endpoint requires int and the others require string I'll propose either keep as string or refactor all the other endpoints to use int

Another thing is the order of endpoints, usually i use the order of (parent, child, grandchild) so since variant is a property of the storeId it should come after store in the list of parameters.

func (service *CheckoutsService) Create(ctx context.Context, storeID, variantID int, attributes *CheckoutCreateAttributes) (*CheckoutApiResponse, *Response, error) {
}

payload := map[string]any{
"data": map[string]any{
"type": "checkouts",
"attributes": map[string]any{
"custom_price": params.CustomPrice,
"product_options": map[string]any{
"enabled_variants": params.EnabledVariants,
},
"checkout_options": map[string]any{
"button_color": params.ButtonColor,
},
"checkout_data": checkoutData,
"expires_at": params.ExpiresAt.Format(time.RFC3339),
"preview": true,
},
"type": "checkouts",
"attributes": attributes,
"relationships": map[string]any{
"store": map[string]any{
"data": map[string]any{
"id": params.StoreID,
"id": strconv.Itoa(storeId),
"type": "stores",
},
},
"variant": map[string]any{
"data": map[string]any{
"id": params.VariantID,
"id": strconv.Itoa(variantId),
"type": "variants",
},
},
Expand Down
27 changes: 17 additions & 10 deletions checkouts_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/NdoleStudio/lemonsqueezy-go/internal/helpers"
"github.com/NdoleStudio/lemonsqueezy-go/internal/stubs"
"github.com/stretchr/testify/assert"
)

func TestCheckoutService_Create(t *testing.T) {
Expand All @@ -20,14 +21,20 @@ func TestCheckoutService_Create(t *testing.T) {
client := New(WithBaseURL(server.URL))

// Act
checkout, response, err := client.Checkouts.Create(context.Background(), &CheckoutCreateParams{
CustomPrice: 5000,
EnabledVariants: []int{1},
ButtonColor: "#2DD272",
CustomData: map[string]string{"user_id": "123"},
ExpiresAt: time.Now().UTC(),
StoreID: "1",
VariantID: "1",
expireAt := time.Now().UTC()
customPrice := 5000
checkout, response, err := client.Checkouts.Create(context.Background(), 1, 1, &CheckoutCreateAttributes{
CustomPrice: &customPrice,
ProductOptions: CheckoutCreateProductOptions{
EnabledVariants: []int{1},
},
CheckoutOptions: CheckoutCreateOptions{
ButtonColor: "2DD272",
},
CheckoutData: CheckoutCreateData{
Custom: map[string]any{"user_id": "123"},
},
ExpiresAt: &expireAt,
})

// Assert
Expand All @@ -50,7 +57,7 @@ func TestCheckoutService_CreateWithError(t *testing.T) {
client := New(WithBaseURL(server.URL))

// Act
_, response, err := client.Checkouts.Create(context.Background(), &CheckoutCreateParams{})
_, response, err := client.Checkouts.Create(context.Background(), 1, 1, &CheckoutCreateAttributes{})

// Assert
assert.NotNil(t, err)
Expand Down
33 changes: 21 additions & 12 deletions e2e/checkouts_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,42 @@ package e2e

import (
"context"
"fmt"
"net/http"
"strconv"
"testing"
"time"

lemonsqueezy "github.com/NdoleStudio/lemonsqueezy-go"
"github.com/stretchr/testify/assert"

"github.com/NdoleStudio/lemonsqueezy-go"
)

func TestCheckoutsService_Create(t *testing.T) {
storeID := 11559
variantId := 36096
expiresAt := time.Now().UTC().Add(time.Hour * 24)
customPrice := 5000

// Act
storeID := "11559"
checkout, response, err := client.Checkouts.Create(context.Background(), &lemonsqueezy.CheckoutCreateParams{
CustomPrice: 5000,
EnabledVariants: []int{36096},
ButtonColor: "#2DD272",
CustomData: map[string]string{"user_id": "123"},
ExpiresAt: time.Now().UTC().Add(time.Hour * 24),
StoreID: storeID,
VariantID: "36096",
checkout, response, err := client.Checkouts.Create(context.Background(), variantId, storeID, &lemonsqueezy.CheckoutCreateAttributes{
CustomPrice: &customPrice,
ProductOptions: lemonsqueezy.CheckoutCreateProductOptions{
EnabledVariants: []int{variantId},
},
CheckoutOptions: lemonsqueezy.CheckoutCreateOptions{
ButtonColor: "#2DD272",
},
CheckoutData: lemonsqueezy.CheckoutCreateData{
Custom: map[string]any{"user_id": "123"},
},
ExpiresAt: &expiresAt,
})

// Assert
assert.Nil(t, err)

assert.Equal(t, http.StatusCreated, response.HTTPResponse.StatusCode)
assert.Equal(t, storeID, fmt.Sprintf("%d", checkout.Data.Attributes.StoreID))
assert.Equal(t, storeID, strconv.Itoa(checkout.Data.Attributes.StoreID))
}

func TestCheckoutsService_Get(t *testing.T) {
Expand Down
Loading