Update subscription_creator to use the new strategy.
When creating subscriptions through the client, detect when the returned error
is saying the account already has a subscription, or the subscription already
exists in stripe.
Add an UpdateSubscription function that will update a subscription through the
API.
Update our subscription_creator listener to listen for profile creation and
login verification messages. This mainly involved fixing the constants (the
system, model, and topic) that the listener for profile creation was listening
for. It also meant adding a new updateMessageHandler that listens for login
verification, tries to create a subscription that has the user ID and email of
the login that was verified, and if a subscription already exists, updates it
instead to use the email address that was just verified. This will ensure that
users get their receipts automatically emailed to them by Stripe.
13 commonAPI "code.secondbit.org/api.hg"
14 "code.secondbit.org/uuid.hg"
16 "code.secondbit.org/ducky/subscriptions.hg/api"
20 ErrNilClient = errors.New("nil client wrapper")
21 ErrNilHTTPClient = errors.New("nil client")
30 func New(address string, id uuid.ID) *Client {
31 address = strings.TrimRight(address, "/")
34 client: &http.Client{},
39 func (c *Client) do(method, url string, request interface{}, scopes []string, subject uuid.ID) (api.Response, error) {
41 return api.Response{}, ErrNilClient
44 return api.Response{}, ErrNilHTTPClient
46 var response api.Response
47 if !strings.HasPrefix(url, "http") {
48 url = strings.TrimLeft(url, "/")
54 data, err := json.Marshal(request)
58 body = bytes.NewBuffer(data)
60 req, err := http.NewRequest(method, url, body)
64 req.Header.Set("Ducky-Scope", strings.Join(scopes, " "))
65 req.Header.Set("Ducky-Issuer", c.ID.String())
67 req.Header.Set("Ducky-Subject", subject.String())
69 req.Header.Set("Ducky-Expires", time.Now().Add(time.Hour).String())
70 req.Header.Set("Ducky-Issued-At", time.Now().String())
71 req.Header.Set("Ducky-Not-Before", time.Now().Add(-5*time.Minute).String())
72 resp, err := c.client.Do(req)
76 defer resp.Body.Close()
77 switch resp.Header.Get("Content-Type") {
78 case "application/json":
79 dec := json.NewDecoder(resp.Body)
80 err = dec.Decode(&response)
85 dec := json.NewDecoder(resp.Body)
86 err = dec.Decode(&response)
91 if len(response.Errors) > 0 {
92 return response, httpErrors(response.Errors)
97 type httpErrors []commonAPI.RequestError
99 func (h httpErrors) Error() string {
100 return fmt.Sprintf("%+#v", h)
103 func (c *Client) Get(url string, scopes []string, subject uuid.ID) (api.Response, error) {
104 return c.do("GET", url, nil, scopes, subject)
107 func (c *Client) Post(url string, request interface{}, scopes []string, subject uuid.ID) (api.Response, error) {
108 return c.do("POST", url, request, scopes, subject)
111 func (c *Client) Patch(url string, request interface{}, scopes []string, subject uuid.ID) (api.Response, error) {
112 return c.do("PATCH", url, request, scopes, subject)