Skip to content

Commit e43a382

Browse files
authored
Merge pull request #13 from igolaizola/licenses-endpoint
Add license activate, validate and deactivate
2 parents d66aeba + a507773 commit e43a382

File tree

5 files changed

+373
-0
lines changed

5 files changed

+373
-0
lines changed

client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type Client struct {
3838
Checkouts *CheckoutsService
3939
LicenseKeys *LicenseKeysService
4040
LicenseKeyInstances *LicenseKeyInstancesService
41+
Licenses *LicensesService
4142
}
4243

4344
// New creates and returns a new Client from a slice of Option.
@@ -73,6 +74,7 @@ func New(options ...Option) *Client {
7374
client.Checkouts = (*CheckoutsService)(&client.common)
7475
client.LicenseKeys = (*LicenseKeysService)(&client.common)
7576
client.LicenseKeyInstances = (*LicenseKeyInstancesService)(&client.common)
77+
client.Licenses = (*LicensesService)(&client.common)
7678

7779
return client
7880
}

internal/stubs/licenses.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package stubs
2+
3+
// LicenseActivateResponse is a dummy response to the POST /v1/licenses/activate endpoint
4+
func LicenseActivateResponse() []byte {
5+
return []byte(`
6+
{
7+
"activated": true,
8+
"error": null,
9+
"license_key": {
10+
"id": 1,
11+
"status": "active",
12+
"key": "38b1460a-5104-4067-a91d-77b872934d51",
13+
"activation_limit": 1,
14+
"activation_usage": 5,
15+
"created_at": "2021-01-24T14:15:07.000000Z",
16+
"expires_at": null
17+
},
18+
"instance": {
19+
"id": "47596ad9-a811-4ebf-ac8a-03fc7b6d2a17",
20+
"name": "Test",
21+
"created_at": "2021-04-06T14:15:07.000000Z"
22+
},
23+
"meta": {
24+
"store_id": 1,
25+
"order_id": 2,
26+
"order_item_id": 3,
27+
"product_id": 4,
28+
"product_name": "Example Product",
29+
"variant_id": 5,
30+
"variant_name": "Default",
31+
"customer_id": 6,
32+
"customer_name": "Luke Skywalker",
33+
"customer_email": "luke@skywalker.com"
34+
}
35+
}
36+
`)
37+
}
38+
39+
// LicenseValidateResponse is a dummy response to the POST /v1/licenses/validate endpoint
40+
func LicenseValidateResponse() []byte {
41+
return []byte(`
42+
{
43+
"valid": true,
44+
"error": null,
45+
"license_key": {
46+
"id": 1,
47+
"status": "active",
48+
"key": "38b1460a-5104-4067-a91d-77b872934d51",
49+
"activation_limit": 1,
50+
"activation_usage": 5,
51+
"created_at": "2021-01-24T14:15:07.000000Z",
52+
"expires_at": "2022-01-24T14:15:07.000000Z"
53+
},
54+
"instance": {
55+
"id": "f90ec370-fd83-46a5-8bbd-44a241e78665",
56+
"name": "Test",
57+
"created_at": "2021-02-24T14:15:07.000000Z"
58+
},
59+
"meta": {
60+
"store_id": 1,
61+
"order_id": 2,
62+
"order_item_id": 3,
63+
"product_id": 4,
64+
"product_name": "Example Product",
65+
"variant_id": 5,
66+
"variant_name": "Default",
67+
"customer_id": 6,
68+
"customer_name": "Luke Skywalker",
69+
"customer_email": "luke@skywalker.com"
70+
}
71+
}
72+
`)
73+
}
74+
75+
// LicenseDeactivateResponse is a dummy response to the POST /v1/licenses/deactivate endpoint
76+
func LicenseDeactivateResponse() []byte {
77+
return []byte(`
78+
{
79+
"deactivated": true,
80+
"error": null,
81+
"license_key": {
82+
"id": 1,
83+
"status": "inactive",
84+
"key": "38b1460a-5104-4067-a91d-77b872934d51",
85+
"activation_limit": 5,
86+
"activation_usage": 0,
87+
"created_at": "2021-01-24T14:15:07.000000Z",
88+
"expires_at": null
89+
},
90+
"meta": {
91+
"store_id": 1,
92+
"order_id": 2,
93+
"order_item_id": 3,
94+
"product_id": 4,
95+
"product_name": "Example Product",
96+
"variant_id": 5,
97+
"variant_name": "Default",
98+
"customer_id": 6,
99+
"customer_name": "Luke Skywalker",
100+
"customer_email": "luke@skywalker.com"
101+
}
102+
}
103+
`)
104+
}

licenses.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package lemonsqueezy
2+
3+
import "time"
4+
5+
type LicenseAttributes struct {
6+
Error string `json:"error"`
7+
LicenseKey LicenseKey `json:"license_key"`
8+
Instance LicenseInstance `json:"instance"`
9+
Meta LicenseMeta `json:"meta"`
10+
}
11+
12+
type LicenseKey struct {
13+
ID int `json:"id"`
14+
Status string `json:"status"`
15+
Key string `json:"key"`
16+
ActivationLimit int `json:"activation_limit"`
17+
ActivationUsage int `json:"activation_usage"`
18+
CreatedAt time.Time `json:"created_at"`
19+
ExpiresAt *time.Time `json:"expires_at"`
20+
TestMode bool `json:"test_mode"`
21+
}
22+
23+
type LicenseInstance struct {
24+
ID string `json:"id"`
25+
Name string `json:"name"`
26+
CreatedAt time.Time `json:"created_at"`
27+
}
28+
29+
type LicenseMeta struct {
30+
StoreID int `json:"store_id"`
31+
OrderID int `json:"order_id"`
32+
OrderItemID int `json:"order_item_id"`
33+
VariantID int `json:"variant_id"`
34+
VariantName string `json:"variant_name"`
35+
ProductID int `json:"product_id"`
36+
ProductName string `json:"product_name"`
37+
CustomerID int `json:"customer_id"`
38+
CustomerName string `json:"customer_name"`
39+
CustomerEmail string `json:"customer_email"`
40+
}
41+
42+
type LicenseActivateApiResponse struct {
43+
Activated bool `json:"activated"`
44+
LicenseAttributes
45+
}
46+
47+
type LicenseValidateApiResponse struct {
48+
Valid bool `json:"valid"`
49+
LicenseAttributes
50+
}
51+
type LicenseDeactivateApiResponse struct {
52+
Deactivated bool `json:"deactivated"`
53+
LicenseAttributes
54+
}

licenses_service.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package lemonsqueezy
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
)
8+
9+
// LicensesService is the API client for the `/v1/licenses` endpoint
10+
type LicensesService service
11+
12+
// Activate a license key.
13+
//
14+
// https://docs.lemonsqueezy.com/help/licensing/license-api
15+
func (service *LicensesService) Activate(ctx context.Context, licenseKey, instanceName string) (*LicenseActivateApiResponse, *Response, error) {
16+
payload := map[string]any{
17+
"license_key": licenseKey,
18+
"instance_name": instanceName,
19+
}
20+
21+
response, err := service.client.do(ctx, http.MethodPost, "/v1/licenses/activate", payload)
22+
if err != nil {
23+
return nil, response, err
24+
}
25+
26+
activation := new(LicenseActivateApiResponse)
27+
if err = json.Unmarshal(*response.Body, activation); err != nil {
28+
return nil, response, err
29+
}
30+
31+
return activation, response, nil
32+
}
33+
34+
// Validate a license key.
35+
//
36+
// https://docs.lemonsqueezy.com/help/licensing/license-api
37+
func (service *LicensesService) Validate(ctx context.Context, licenseKey, instanceID string) (*LicenseValidateApiResponse, *Response, error) {
38+
payload := map[string]any{
39+
"license_key": licenseKey,
40+
"instance_id": instanceID,
41+
}
42+
43+
response, err := service.client.do(ctx, http.MethodPost, "/v1/licenses/validate", payload)
44+
if err != nil {
45+
return nil, response, err
46+
}
47+
48+
validation := new(LicenseValidateApiResponse)
49+
if err = json.Unmarshal(*response.Body, validation); err != nil {
50+
return nil, response, err
51+
}
52+
53+
return validation, response, nil
54+
}
55+
56+
// Deactivate a license key.
57+
//
58+
// https://docs.lemonsqueezy.com/help/licensing/license-api
59+
func (service *LicensesService) Deactivate(ctx context.Context, licenseKey, instanceID string) (*LicenseDeactivateApiResponse, *Response, error) {
60+
payload := map[string]any{
61+
"license_key": licenseKey,
62+
"instance_id": instanceID,
63+
}
64+
65+
response, err := service.client.do(ctx, http.MethodPost, "/v1/licenses/deactivate", payload)
66+
if err != nil {
67+
return nil, response, err
68+
}
69+
70+
deactivation := new(LicenseDeactivateApiResponse)
71+
if err = json.Unmarshal(*response.Body, deactivation); err != nil {
72+
return nil, response, err
73+
}
74+
75+
return deactivation, response, nil
76+
}

licenses_service_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package lemonsqueezy
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"testing"
7+
8+
"github.com/NdoleStudio/lemonsqueezy-go/internal/helpers"
9+
"github.com/NdoleStudio/lemonsqueezy-go/internal/stubs"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestLicensesService_Activate(t *testing.T) {
14+
// Setup
15+
t.Parallel()
16+
17+
// Arrange
18+
server := helpers.MakeTestServer(http.StatusOK, stubs.LicenseActivateResponse())
19+
client := New(WithBaseURL(server.URL))
20+
21+
// Act
22+
licenseActivation, response, err := client.Licenses.Activate(context.Background(), "1234567890", "test")
23+
24+
// Assert
25+
assert.Nil(t, err)
26+
27+
assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode)
28+
assert.Equal(t, stubs.LicenseActivateResponse(), *response.Body)
29+
assert.Equal(t, true, licenseActivation.Activated)
30+
31+
// Teardown
32+
server.Close()
33+
}
34+
35+
func TestLicensesService_ActivateWithError(t *testing.T) {
36+
// Setup
37+
t.Parallel()
38+
39+
// Arrange
40+
server := helpers.MakeTestServer(http.StatusInternalServerError, nil)
41+
client := New(WithBaseURL(server.URL))
42+
43+
// Act
44+
_, response, err := client.Licenses.Activate(context.Background(), "1234567890", "test")
45+
46+
// Assert
47+
assert.NotNil(t, err)
48+
49+
assert.Equal(t, http.StatusInternalServerError, response.HTTPResponse.StatusCode)
50+
51+
// Teardown
52+
server.Close()
53+
}
54+
55+
func TestLicensesService_Validate(t *testing.T) {
56+
// Setup
57+
t.Parallel()
58+
59+
// Arrange
60+
server := helpers.MakeTestServer(http.StatusOK, stubs.LicenseValidateResponse())
61+
client := New(WithBaseURL(server.URL))
62+
63+
// Act
64+
licenseValidation, response, err := client.Licenses.Validate(context.Background(), "1234567890", "")
65+
66+
// Assert
67+
assert.Nil(t, err)
68+
69+
assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode)
70+
assert.Equal(t, stubs.LicenseValidateResponse(), *response.Body)
71+
assert.Equal(t, true, licenseValidation.Valid)
72+
73+
// Teardown
74+
server.Close()
75+
}
76+
77+
func TestLicensesService_ValidateWithError(t *testing.T) {
78+
// Setup
79+
t.Parallel()
80+
81+
// Arrange
82+
server := helpers.MakeTestServer(http.StatusInternalServerError, nil)
83+
client := New(WithBaseURL(server.URL))
84+
85+
// Act
86+
_, response, err := client.Licenses.Validate(context.Background(), "1234567890", "")
87+
88+
// Assert
89+
assert.NotNil(t, err)
90+
91+
assert.Equal(t, http.StatusInternalServerError, response.HTTPResponse.StatusCode)
92+
93+
// Teardown
94+
server.Close()
95+
}
96+
97+
func TestLicensesService_Deactivate(t *testing.T) {
98+
// Setup
99+
t.Parallel()
100+
101+
// Arrange
102+
server := helpers.MakeTestServer(http.StatusOK, stubs.LicenseDeactivateResponse())
103+
client := New(WithBaseURL(server.URL))
104+
105+
// Act
106+
licenseDeactivation, response, err := client.Licenses.Deactivate(context.Background(), "1234567890", "abc123")
107+
108+
// Assert
109+
assert.Nil(t, err)
110+
111+
assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode)
112+
assert.Equal(t, stubs.LicenseDeactivateResponse(), *response.Body)
113+
assert.Equal(t, true, licenseDeactivation.Deactivated)
114+
115+
// Teardown
116+
server.Close()
117+
}
118+
119+
func TestLicensesService_DeactivateWithError(t *testing.T) {
120+
// Setup
121+
t.Parallel()
122+
123+
// Arrange
124+
server := helpers.MakeTestServer(http.StatusInternalServerError, nil)
125+
client := New(WithBaseURL(server.URL))
126+
127+
// Act
128+
_, response, err := client.Licenses.Deactivate(context.Background(), "1234567890", "abc123")
129+
130+
// Assert
131+
assert.NotNil(t, err)
132+
133+
assert.Equal(t, http.StatusInternalServerError, response.HTTPResponse.StatusCode)
134+
135+
// Teardown
136+
server.Close()
137+
}

0 commit comments

Comments
 (0)