tebex

package module
v0.0.0-...-eb65867 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 15, 2024 License: MIT Imports: 13 Imported by: 0

README

tebex-go

license

A small Tebex webhook & partial headless API implementation for Go. Includes all the parts to validate and parse a Tebex webhook message, as well as a partial client implementation of the headless api for creating baskets/checkout links.

Install

go get github.com/hollow-cube/tebex-go@latest

Webhook Processing

func Handle(r *http.Request, w http.ResponseWriter) error {
    // Validate the payload from the request
    // May want to set the last arg (checkIp) to false if using a proxy. If true, it will check if the request
    // came from one of the known Tebex IPs. These IPs are also available at tebex.TebexWebhookIpAddresses.
    body, err := tebex.ValidatePayload(r, []byte("my-secret"), true)
    if err != nil {
        return err		
    }
	
    // Parse the payload 
    event, err := tebex.ParseEvent(body)
    if err != nil {
        return err
    }
    
    // Handle the event
    switch subject := event.Subject.(type) {
    case *tebex.PaymentCompletedEvent: 
        // Handle payment completed event
    }
    
    return nil
}

Headless API

The headless API is not completely supported, the supported endpoints can be found below. More information about the headless api can be found on the official documentation.

// Obtain the default client (using the official endpoint & http.DefaultClient)
headless := tebex.DefaultHeadlessClient

// Create a new basket
basket, err := headless.CreateBasket(ctx, myWebstoreId, tebex.HeadlessCreateBasketRequest{Username: "notmattw"})

// Add a package to the basket
basket, err := headless.BasketAddPackage(ctx, basket.Ident, tebex.HeadlessBasketAddPackageRequest{PackageId: 789, Quantity: 1})

// Add a creator code to the basket
err := headless.BasketApplyCreatorCode(ctx, myWebstoreId, basket.Ident, "myCreatorCode")

// Remove any applied creator code from the basket
err := headless.BasketRemoveCreatorCode(ctx, myWebstoreId, basket.Ident)

// Get the checkout link for the basket (will not be present until at least one package is added)
checkoutUrl := basket.Links.Checkout

Contributing

Contibutions in the form of bug reports (via issues) or pull requests are welcome. Discussion of the project can be done in the Hollow Cube Discord Server.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Index

Constants

View Source
const DefaultBaseUrl = "https://headless.tebex.io"

Variables

View Source
var (
	ErrHeadlessWebstoreNotFound    = fmt.Errorf("webstore not found")
	ErrHeadlessBasketNotFound      = fmt.Errorf("basket not found")
	ErrHeadlessCreatorCodeNotFound = fmt.Errorf("creator code not found")
)
View Source
var (
	ErrInvalidIp          = errors.New("invalid remote ip address")
	ErrInvalidContentType = errors.New("invalid content type")
	ErrMissingSignature   = errors.New("missing signature")
	ErrInvalidSignature   = errors.New("invalid signature")

	TebexWebhookIpAddresses = []string{"18.209.80.3", "54.87.231.232"}
)
View Source
var DefaultHeadlessClient = NewHeadlessClient(DefaultBaseUrl)

Functions

func ValidatePayload

func ValidatePayload(r *http.Request, secret []byte, checkIp bool) ([]byte, error)

func ValidatePayloadRaw

func ValidatePayloadRaw(contentType string, body []byte, signature string, secret []byte, remoteAddr string) error

Types

type Customer

type Customer struct {
	FirstName        string `json:"first_name"`
	LastName         string `json:"last_name"`
	Email            string `json:"email"`
	Ip               string `json:"ip"`
	Username         User   `json:"username"`
	MarketingConsent bool   `json:"marketing_consent"`
	Country          string `json:"country"`
	PostalCode       string `json:"postal_code"`
}

type DeclineReason

type DeclineReason struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

type Event

type Event struct {
	Id      string      `json:"id"`
	Type    EventType   `json:"type"`
	Date    time.Time   `json:"date"`
	Subject interface{} `json:"subject"`
}

Event is the common wrapper around any Tebex event

func ParseEvent

func ParseEvent(payload []byte) (*Event, error)

type EventType

type EventType string
const (
	ValidationEventType                    EventType = "validation.webhook"
	PaymentCompletedEventType              EventType = "payment.completed"
	PaymentDeclinedEventType               EventType = "payment.declined"
	PaymentRefundedEventType               EventType = "payment.refunded"
	PaymentDisputeOpenedEventType          EventType = "payment.dispute.opened"
	PaymentDisputeWonEventType             EventType = "payment.dispute.won"
	PaymentDisputeLostEventType            EventType = "payment.dispute.lost"
	PaymentDisputeClosedEventType          EventType = "payment.dispute.closed"
	RecurringPaymentStartedEventType       EventType = "recurring-payment.started"
	RecurringPaymentRenewedEventType       EventType = "recurring-payment.renewed"
	RecurringPaymentEndedEventType         EventType = "recurring-payment.ended"
	RecurringPaymentStatusChangedEventType EventType = "recurring-payment.status-changed"
)

type HeadlessBasket

type HeadlessBasket struct {
	Ident       string               `json:"ident"`
	Complete    bool                 `json:"complete"`
	Id          int                  `json:"id"`
	Country     string               `json:"country"`
	Ip          string               `json:"ip"`
	UsernameId  string               `json:"username_id"`
	Username    string               `json:"username"`
	BasePrice   float64              `json:"base_price"`
	SalesTax    float64              `json:"sales_tax"`
	TotalPrice  float64              `json:"total"`
	Packages    []any                `json:"packages"`
	Coupons     []any                `json:"coupons"`
	GiftCards   []any                `json:"gift_cards"`
	CreatorCode string               `json:"creator_code"`
	Links       *HeadlessBasketLinks `json:"links"`
}

type HeadlessBasketAddPackageRequest

type HeadlessBasketAddPackageRequest struct {
	PackageId    int            `json:"package_id"`
	Quantity     int            `json:"quantity"`
	VariableData map[string]any `json:"variable_data,omitempty"`
}
type HeadlessBasketLinks struct {
	Checkout string `json:"checkout"`
}

func (*HeadlessBasketLinks) UnmarshalJSON

func (h *HeadlessBasketLinks) UnmarshalJSON(raw []byte) error

type HeadlessClient

type HeadlessClient struct {
	// contains filtered or unexported fields
}

func NewHeadlessClient

func NewHeadlessClient(url string) *HeadlessClient

func NewHeadlessClientWithOptions

func NewHeadlessClientWithOptions(params HeadlessClientParams) *HeadlessClient

func (*HeadlessClient) BasketAddPackage

func (c *HeadlessClient) BasketAddPackage(ctx context.Context, basketId string, body HeadlessBasketAddPackageRequest) (*HeadlessBasket, error)

func (*HeadlessClient) BasketApplyCreatorCode

func (c *HeadlessClient) BasketApplyCreatorCode(ctx context.Context, webstoreId, basketId, creatorCode string) error

func (*HeadlessClient) BasketRemoveCreatorCode

func (c *HeadlessClient) BasketRemoveCreatorCode(ctx context.Context, webstoreId, basketId string) error

func (*HeadlessClient) CreateBasket

func (c *HeadlessClient) CreateBasket(ctx context.Context, webstoreId string, body HeadlessCreateBasketRequest) (*HeadlessBasket, error)

type HeadlessClientParams

type HeadlessClientParams struct {
	Url        string // Required
	HttpClient *http.Client
}

type HeadlessCreateBasketRequest

type HeadlessCreateBasketRequest struct {
	CompleteUrl          string         `json:"complete_url,omitempty"`
	CompleteAutoRedirect bool           `json:"complete_auto_redirect,omitempty"`
	CancelUrl            string         `json:"cancel_url,omitempty"`
	Custom               map[string]any `json:"custom,omitempty"`

	// Should be present for Minecraft webstores.
	Username string `json:"username,omitempty"`
}

type Payment

type Payment struct {
	TransactionId             string             `json:"transaction_id"`
	Status                    PaymentStatus      `json:"status"`
	PaymentSequence           PaymentSequence    `json:"payment_sequence"`
	CreatedAt                 time.Time          `json:"created_at"`
	Price                     Price              `json:"price"`
	PricePaid                 Price              `json:"price_paid"`
	PaymentMethod             PaymentMethod      `json:"payment_method"`
	Fees                      map[string]Price   `json:"fees"`
	Customer                  Customer           `json:"customer"`
	Products                  []*ProductPurchase `json:"products"`
	Coupons                   []interface{}      `json:"coupons"`                     // todo
	GiftCards                 []interface{}      `json:"gift_cards"`                  // todo
	RecurringPaymentReference *string            `json:"recurring_payment_reference"` // Only present for recurring payments
	DeclineReason             *DeclineReason     `json:"decline_reason"`              // Only present for payment.declined (I think)
}

type PaymentCompletedEvent

type PaymentCompletedEvent Payment

type PaymentDeclinedEvent

type PaymentDeclinedEvent Payment

type PaymentDisputeClosedEvent

type PaymentDisputeClosedEvent Payment

type PaymentDisputeLostEvent

type PaymentDisputeLostEvent Payment

type PaymentDisputeOpenedEvent

type PaymentDisputeOpenedEvent Payment

type PaymentDisputeWonEvent

type PaymentDisputeWonEvent Payment

type PaymentMethod

type PaymentMethod struct {
	Name       string `json:"name"`
	Refundable bool   `json:"refundable"`
}

type PaymentRefundedEvent

type PaymentRefundedEvent Payment

type PaymentSequence

type PaymentSequence string
const (
	PaymentSequenceOneOff PaymentSequence = "oneoff"
	PaymentSequenceFirst  PaymentSequence = "first"
)

type PaymentStatus

type PaymentStatus struct {
	Id          PaymentStatusType `json:"id"`
	Description string            `json:"description"`
}

type PaymentStatusType

type PaymentStatusType int
const (
	PaymentStatus__0 PaymentStatusType = iota // Unknown, is there a 0 entry??
	PaymentStatusComplete
	PaymentStatusRefund
	PaymentStatus__3 // Unknown
	PaymentStatus__4 // Unknown
	PaymentStatusCancelled
)

type Price

type Price struct {
	Amount   float64 `json:"amount"`
	Currency string  `json:"currency"`
}

type ProductPurchase

type ProductPurchase struct {
	Id        int         `json:"id"`
	Name      string      `json:"name"`
	Quantity  int         `json:"quantity"`
	BasePrice Price       `json:"base_price"`
	PaidPrice Price       `json:"paid_price"`
	Variables []*Variable `json:"variables"`
	ExpiresAt *time.Time  `json:"expires_at"`
	Custom    *string     `json:"custom"`
	Username  User        `json:"username"`
}

type RecurringPayment

type RecurringPayment struct {
	Reference      string        `json:"reference"`
	CreatedAt      time.Time     `json:"created_at"`
	NextPaymentAt  time.Time     `json:"next_payment_at"`
	Status         PaymentStatus `json:"status"`
	InitialPayment Payment       `json:"initial_payment"`
	LastPayment    Payment       `json:"last_payment"`
	FailCount      int           `json:"fail_count"`
	Price          Price         `json:"price"`
	CancelledAt    *time.Time    `json:"cancelled_at"`  // Only present if Status.Id == PaymentStatusCancelled
	CancelReason   *string       `json:"cancel_reason"` // Only present if Status.Id == PaymentStatusCancelled
}

type RecurringPaymentEndedEvent

type RecurringPaymentEndedEvent RecurringPayment

type RecurringPaymentRenewedEvent

type RecurringPaymentRenewedEvent RecurringPayment

type RecurringPaymentStartedEvent

type RecurringPaymentStartedEvent RecurringPayment

type RecurringPaymentStatusChangedEvent

type RecurringPaymentStatusChangedEvent RecurringPayment

type TebexWebhookData

type TebexWebhookData struct {
	ContentType string
	Secret      []byte
	Signature   string
	Body        []byte
}

type User

type User struct {
	Id       string `json:"id"`
	Username string `json:"username"`
}

type ValidationEvent

type ValidationEvent struct {
}

ValidationEvent is sent by Tebex when the webhook is added to the project to verify that it expects to receive Tebex webhooks.

type = `validation.webhook`

type Variable

type Variable struct {
	Identifier string `json:"identifier"`
	Option     string `json:"option"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL