1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
package billing
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/stripe/stripe-go/v86"
)
const (
stripeAPIBaseURL = "https://api.stripe.com"
maxStripeErrorBodySize = 64 << 10
)
var ErrLiveStripeKey = errors.New("Stripe permission preflight only accepts test-mode keys")
type StripePermissionCheck struct {
Name string `json:"name"`
OK bool `json:"ok"`
StatusCode int `json:"status_code"`
ErrorCode string `json:"error_code,omitempty"`
ErrorType string `json:"error_type,omitempty"`
}
type StripePreflightResult struct {
APIVersion string `json:"api_version"`
TestMode bool `json:"test_mode"`
Ready bool `json:"ready"`
Checks []StripePermissionCheck `json:"checks"`
}
type stripeReadCheck struct {
name string
path string
}
var stripeRequiredReadChecks = []stripeReadCheck{
{name: "customers_read", path: "/v1/customers"},
{name: "checkout_sessions_read", path: "/v1/checkout/sessions"},
{name: "setup_intents_read", path: "/v1/setup_intents"},
{name: "payment_intents_read", path: "/v1/payment_intents"},
{name: "refunds_read", path: "/v1/refunds"},
{name: "charges_read", path: "/v1/charges"},
{name: "disputes_read", path: "/v1/disputes"},
{name: "invoices_read", path: "/v1/invoices"},
{name: "billing_portal_configurations_read", path: "/v1/billing_portal/configurations"},
}
// CheckStripePermissions validates the read side of the restricted-key contract
// without creating Stripe objects. Write permissions are exercised by the
// sandbox Checkout, Portal, automatic top-up, refund, and reconciliation flows.
func CheckStripePermissions(ctx context.Context, apiKey string) (StripePreflightResult, error) {
return checkStripePermissions(ctx, apiKey, stripeAPIBaseURL, http.DefaultClient)
}
func checkStripePermissions(ctx context.Context, apiKey, baseURL string, client *http.Client) (StripePreflightResult, error) {
apiKey = strings.TrimSpace(apiKey)
result := StripePreflightResult{
APIVersion: stripe.APIVersion,
TestMode: isStripeTestKey(apiKey),
Checks: make([]StripePermissionCheck, 0, len(stripeRequiredReadChecks)),
}
if !result.TestMode {
return result, ErrLiveStripeKey
}
result.Ready = true
if client == nil {
client = http.DefaultClient
}
for _, check := range stripeRequiredReadChecks {
item := runStripeReadCheck(ctx, client, apiKey, baseURL, check)
result.Checks = append(result.Checks, item)
result.Ready = result.Ready && item.OK
}
return result, nil
}
func runStripeReadCheck(ctx context.Context, client *http.Client, apiKey, baseURL string, check stripeReadCheck) StripePermissionCheck {
endpoint, err := url.JoinPath(baseURL, check.path)
if err != nil {
return StripePermissionCheck{Name: check.name, ErrorType: "configuration_error"}
}
query := url.Values{"limit": []string{"1"}}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint+"?"+query.Encode(), nil)
if err != nil {
return StripePermissionCheck{Name: check.name, ErrorType: "configuration_error"}
}
request.Header.Set("Authorization", "Bearer "+apiKey)
request.Header.Set("Stripe-Version", stripe.APIVersion)
response, err := client.Do(request)
if err != nil {
return StripePermissionCheck{Name: check.name, ErrorType: "network_error"}
}
defer response.Body.Close()
item := StripePermissionCheck{Name: check.name, OK: response.StatusCode >= 200 && response.StatusCode < 300, StatusCode: response.StatusCode}
if item.OK {
_, _ = io.Copy(io.Discard, response.Body)
return item
}
var envelope struct {
Error struct {
Code string `json:"code"`
Type string `json:"type"`
} `json:"error"`
}
if err := json.NewDecoder(io.LimitReader(response.Body, maxStripeErrorBodySize)).Decode(&envelope); err == nil {
item.ErrorCode = envelope.Error.Code
item.ErrorType = envelope.Error.Type
}
if item.ErrorType == "" {
item.ErrorType = fmt.Sprintf("http_%d", response.StatusCode)
}
return item
}
func isStripeTestKey(value string) bool {
return strings.HasPrefix(value, "rk_test_") || strings.HasPrefix(value, "sk_test_")
}
|