• Pricing
  • Blog
Log inBook a demo
Introduction

Quickstart

Create an API KeyChoose a Billing Model
Integrate with GoIntegrate with Encore

Learn

Resources

SDK ReferenceAPI VersioningError HandlingTestingCLIExamples

Plugins

Better Auth
System status
DocumentationKnowledge BaseBuild with AIAPI ReferenceWebhooks

Integrate with Encore

Add billing and payments to your Encore application.

Install the Commet Skill so your coding agent can integrate the current SDK and verify its work against the live API contract.

npx skills add commet-labs/skills --skill commet

Install

encore app create --example=hello-world myapp
cd myapp
go get github.com/commet-labs/commet-go/v9

Configure

Store secrets using Encore's secret manager instead of environment variables:

encore secret set --type dev,local,pr,prod CommetAPIKey
encore secret set --type dev,local,pr,prod CommetWebhookSecret
billing/billing.go
package billing

import (
	commet "github.com/commet-labs/commet-go/v9"
)

var secrets struct {
	CommetAPIKey       string
	CommetWebhookSecret string
}

var client *commet.Client

func initClient() error {
	var err error
	client, err = commet.New(secrets.CommetAPIKey)
	return err
}

There is no environment option on the client: sandbox vs live is decided by the organization the API key belongs to.

Subscribe

billing/billing.go
import "context"

type SubscribeParams struct {
	Email      string `json:"email"`
	CustomerID string `json:"customer_id"`
}

type SubscribeResponse struct {
	CheckoutURL string `json:"checkout_url"`
}

//encore:api public method=POST path=/billing/subscribe
func Subscribe(ctx context.Context, req *SubscribeParams) (*SubscribeResponse, error) {
	if err := initClient(); err != nil {
		return nil, err
	}

	_, err := client.Customers.Create(ctx, &commet.CreateCustomerParams{
		Email: req.Email,
		ID:    &req.CustomerID,
	})
	if err != nil {
		return nil, err
	}

	planCode := "pro"
	subscription, err := client.Subscriptions.Create(ctx, &commet.CreateSubscriptionParams{
		CustomerID: req.CustomerID,
		PlanCode:   &planCode,
	})
	if err != nil {
		return nil, err
	}

	checkoutURL := ""
	if subscription.CheckoutURL != nil {
		checkoutURL = *subscription.CheckoutURL
	}

	return &SubscribeResponse{
		CheckoutURL: checkoutURL,
	}, nil
}

Check Access

billing/billing.go
type SubscriptionResponse struct {
	Status string `json:"status"`
}

//encore:api public method=GET path=/billing/subscription/:customerID
func GetSubscription(ctx context.Context, customerID string) (*SubscriptionResponse, error) {
	if err := initClient(); err != nil {
		return nil, err
	}

	sub, err := client.Subscriptions.GetActive(ctx, &commet.GetActiveSubscriptionParams{
		CustomerID: customerID,
	})
	if err != nil {
		return nil, err
	}

	if sub == nil {
		return &SubscriptionResponse{Status: "none"}, nil
	}

	return &SubscriptionResponse{
		Status: string(sub.Status),
	}, nil
}

type FeatureResponse struct {
	Allowed bool `json:"allowed"`
}

//encore:api public method=GET path=/billing/features/:feature/:customerID
func CheckFeature(ctx context.Context, feature string, customerID string) (*FeatureResponse, error) {
	if err := initClient(); err != nil {
		return nil, err
	}

	result, err := client.FeatureAccess.Get(ctx, feature, &commet.GetFeatureAccessParams{
		CustomerID: customerID,
	})
	if err != nil {
		return nil, err
	}

	return &FeatureResponse{
		Allowed: result.Allowed,
	}, nil
}

Track Usage

billing/billing.go
func float64Ptr(value float64) *float64 { return &value }

type UsageParams struct {
	CustomerID string `json:"customer_id"`
}

type UsageResponse struct {
	Tracked bool `json:"tracked"`
}

//encore:api public method=POST path=/billing/usage
func TrackUsage(ctx context.Context, req *UsageParams) (*UsageResponse, error) {
	if err := initClient(); err != nil {
		return nil, err
	}

	_, err := client.Usage.Track(ctx, &commet.TrackUsageParams{
		CustomerID: req.CustomerID,
		FeatureCode: "api_calls",
		Value:       float64Ptr(1),
	})
	if err != nil {
		return nil, err
	}

	return &UsageResponse{Tracked: true}, nil
}

Usage is aggregated and billed at end of period.

Webhooks

billing/webhooks.go
package billing

import (

	"io"
	"net/http"

	commet "github.com/commet-labs/commet-go/v9"
)

//encore:api public raw method=POST path=/webhooks/commet
func HandleWebhook(w http.ResponseWriter, r *http.Request) {
	rawBody, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to read body", http.StatusBadRequest)
		return
	}

	webhooks := &commet.WebhooksResource{}
	payload, err := webhooks.VerifyAndParse(
		string(rawBody),
		r.Header.Get("x-commet-signature"),
		secrets.CommetWebhookSecret,
	)
	if err != nil {
		http.Error(w, "Invalid signature", http.StatusUnauthorized)
		return
	}

	switch payload["event"] {
	case "subscription.activated":
		// handle activation
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write([]byte(`{"ok":true}`))
}

Run

encore run

Related

  • Subscriptions
  • Track Usage
  • Customer Portal
  • SDK Reference

How is this guide?

Integrate with Go

Add billing and payments to your Go application using net/http.

Integrate with PHP

Install and configure the Commet PHP SDK.

On this page

Install
Configure
Subscribe
Check Access
Track Usage
Webhooks
Run
Related