Skip to content

Commit

Permalink
raw_request.Client + V2 UBB Examples (#1929)
Browse files Browse the repository at this point in the history
* added rawrequest.Client type and associated functions, to let us specify a backend and key with which to make raw requests

* added GetRawRequestBackend helper function to stripe, to get back a RawRequestBackend or error

* added MeterEventsBackend and MeterEventsURL to constants, and added MeterEventsBackend to GetBackendWithConfig switch statement

* added UBB examples
  • Loading branch information
jar-stripe authored Oct 8, 2024
1 parent 1ba5c95 commit c4e7524
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 0 deletions.
101 changes: 101 additions & 0 deletions example/v2/meter_event_stream/meter_event_stream.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package main

import (
"encoding/json"
"fmt"
"net/http"
"os"
"time"

stripe "github.com/stripe/stripe-go/v80"
rawrequest "github.com/stripe/stripe-go/v80/rawrequest"
)

var sessionAuthToken string = ""
var sessionAuthExpiresAt string = ""

func refreshMeterEventSession(client rawrequest.Client) (err error) {
currentTime := time.Now().Format(time.RFC3339)
// Check if session is null or expired
if sessionAuthToken == "" || sessionAuthExpiresAt <= currentTime {
// Create a new meter event session in case the existing session expired
rawResp, err := client.RawRequest(http.MethodPost, "/v2/billing/meter_event_session", "", nil)
if err != nil {
return err
}
if rawResp.StatusCode != 200 {
return fmt.Errorf(rawResp.Status)
}

var resp map[string]interface{}
err = json.Unmarshal(rawResp.RawJSON, &resp)
if err != nil {
return err
}

sessionAuthToken = resp["authentication_token"].(string)
sessionAuthExpiresAt = resp["expires_at"].(string)

fmt.Println("Meter event session created!")
}
return nil
}

func sendMeterEvent(client rawrequest.Client, eventName string, stripeCustomerID string, value string) (err error) {
// Refresh the meter event session if necessary
refreshMeterEventSession(client)

if sessionAuthToken == "" {
err = fmt.Errorf("Unable to refresh meter event session")
return
}

b, err := stripe.GetRawRequestBackend(stripe.MeterEventsBackend)
if err != nil {
return err
}

sessionClient := rawrequest.Client{B: b, Key: sessionAuthToken}

params := map[string]interface{}{
"events": []interface{}{
map[string]interface{}{
"event_name": eventName,
"payload": map[string]interface{}{
"stripe_customer_id": stripeCustomerID,
"value": value,
},
},
},
}

contentBytes, err := json.Marshal(params)
if err != nil {
return
}

content := string(contentBytes)
_, err = sessionClient.RawRequest(http.MethodPost, "/v2/billing/meter_event_stream", content, nil)
return
}

func main() {

apiKey := "{{API_KEY}}"
customerID := "{{CUSTOMER_ID}}"

b, err := stripe.GetRawRequestBackend(stripe.APIBackend)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

client := rawrequest.Client{B: b, Key: apiKey}

err = sendMeterEvent(client, "api_requests", customerID, "25")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println("Meter event sent successfully!")
}
95 changes: 95 additions & 0 deletions example/v2/thinevent_webhook_handler/thinevent_webhook_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"

"github.com/stripe/stripe-go/v80"
billingMeters "github.com/stripe/stripe-go/v80/billing/meter"
"github.com/stripe/stripe-go/v80/rawrequest"
webhook "github.com/stripe/stripe-go/v80/webhook"
)

var apiKey = "{{API_KEY}}"
var webhookSecret = "{{WEBHOOK_SECRET}}"

func main() {
b, err := stripe.GetRawRequestBackend(stripe.APIBackend)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

client := rawrequest.Client{B: b, Key: apiKey}

http.HandleFunc("/webhook", func(w http.ResponseWriter, req *http.Request) {
const MaxBodyBytes = int64(65536)
req.Body = http.MaxBytesReader(w, req.Body, MaxBodyBytes)
payload, err := ioutil.ReadAll(req.Body)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

err = webhook.ValidatePayload(payload, req.Header.Get("Stripe-Signature"), webhookSecret)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

var thinEvent map[string]interface{}

if err := json.Unmarshal(payload, &thinEvent); err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse thin event body json: %v\n", err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}

eventID := thinEvent["id"].(string)

var event map[string]interface{}

resp, err := client.RawRequest(http.MethodGet, "/v2/core/events/"+eventID, "", nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get pull event: %v\n", err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}

if err := json.Unmarshal(resp.RawJSON, &event); err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse pull event body json: %v\n", err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
// Unmarshal the event data into an appropriate struct depending on its Type
switch t := event["type"].(string); t {
case "v1.billing.meter.error_report_triggered":
relatedObject := event["related_object"].(map[string]interface{})
meter, err := billingMeters.Get(relatedObject["id"].(string), nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get related meter object: %v\n", err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}

meterID := meter.ID
fmt.Printf("Success! %s\n", meterID)
// Verify we can see event data
fmt.Println(fmt.Sprint(event["data"]))
default:
fmt.Fprintf(os.Stderr, "Unhandled event type: %s\n", t)
}

w.WriteHeader(http.StatusOK)
})
err = http.ListenAndServe(":4242", nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
12 changes: 12 additions & 0 deletions rawrequest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,24 @@ import (
stripe "github.com/stripe/stripe-go/v80"
)

// Client is used to invoke raw requests against the specified backend
type Client struct {
B stripe.RawRequestBackend
Key string
}

func (c Client) RawRequest(method string, path string, content string, params *stripe.RawParams) (*stripe.APIResponse, error) {
return c.B.RawRequest(method, path, c.Key, content, params)
}

func Get(path string, params *stripe.RawParams) (*stripe.APIResponse, error) {
return stripe.RawRequest(http.MethodGet, path, "", params)
}

func Post(path, content string, params *stripe.RawParams) (*stripe.APIResponse, error) {
return stripe.RawRequest(http.MethodPost, path, content, params)
}

func Delete(path string, params *stripe.RawParams) (*stripe.APIResponse, error) {
return stripe.RawRequest(http.MethodDelete, path, "", params)
}
5 changes: 5 additions & 0 deletions scripts/check_api_clients/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ func main() {
return nil
}

// rawrequest client is not exposed this way
if strings.HasSuffix(path, "rawrequest/client.go") {
return nil
}

packageName, err := findClientType(fset, path)
if err != nil {
exitWithError(err)
Expand Down
20 changes: 20 additions & 0 deletions stripe.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ const (
// by a Stripe client.
DefaultMaxNetworkRetries int64 = 2

MeterEventsBackend SupportedBackend = "meterevents"

MeterEventsURL = "https://meter-events.stripe.com"

// UnknownPlatform is the string returned as the system name if we couldn't get
// one from `uname`.
UnknownPlatform string = "unknown platform"
Expand Down Expand Up @@ -1227,12 +1231,28 @@ func GetBackendWithConfig(backendType SupportedBackend, config *BackendConfig) B

cfg.URL = String(normalizeURL(*cfg.URL))

return newBackendImplementation(backendType, &cfg)

case MeterEventsBackend:
if cfg.URL == nil {
cfg.URL = String(MeterEventsURL)
}

cfg.URL = String(normalizeURL(*cfg.URL))

return newBackendImplementation(backendType, &cfg)
}

return nil
}

func GetRawRequestBackend(backendType SupportedBackend) (RawRequestBackend, error) {
if bi, ok := GetBackend(backendType).(RawRequestBackend); ok {
return bi, nil
}
return nil, fmt.Errorf("Error: cannot call RawRequest if requested backend type is initialized with a backend that doesn't implement RawRequestBackend")
}

// Int64 returns a pointer to the int64 value passed in.
func Int64(v int64) *int64 {
return &v
Expand Down

0 comments on commit c4e7524

Please sign in to comment.