-
Notifications
You must be signed in to change notification settings - Fork 1
/
stripe.go
216 lines (183 loc) · 5.24 KB
/
stripe.go
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package pay
import (
"errors"
"fmt"
"time"
"github.com/stripe/stripe-go/v74"
"github.com/stripe/stripe-go/v74/checkout/session"
"github.com/stripe/stripe-go/v74/customer"
"github.com/stripe/stripe-go/v74/price"
"github.com/stripe/stripe-go/v74/product"
)
const ProviderStripe = "stripe"
var ErrCheckoutFailed = errors.New("checkout failed")
type (
// StripeConfig configures StripeService with necessary credentials and callbacks
StripeConfig struct {
Repo *Repo
Key string
WebhookSecret string
}
// StripeProvider interfaces with stripe for customer, plan and subscription data
StripeProvider struct {
*Repo
config *StripeConfig
}
)
// NewStripeProvider creates a provider service for interacting with stripe
func NewStripeProvider(config *StripeConfig) *StripeProvider {
if config == nil {
config = new(StripeConfig)
}
stripe.Key = config.Key
return &StripeProvider{
Repo: config.Repo,
config: config,
}
}
// AddPlan directly in stripe
func (s *StripeProvider) AddPlan(p *Plan) error {
_, err := product.New(&stripe.ProductParams{
Name: stripe.String(p.Name),
Description: stripe.String(p.Description),
Active: stripe.Bool(p.Active),
})
return err
}
// UpdatePlan in stripe
func (s *StripeProvider) UpdatePlan(p *Plan) error {
_, err := product.Update(p.ProviderID, &stripe.ProductParams{
Name: stripe.String(p.Name),
Description: stripe.String(p.Description),
Active: stripe.Bool(p.Active),
})
return err
}
// RemovePlan from stripe
func (s *StripeProvider) RemovePlanByProviderID(providerID string) error {
_, err := product.Del(providerID, nil)
return err
}
// AddPrice directly in stripe
func (s *StripeProvider) AddPrice(p *Price) error {
var sched string
switch p.Schedule {
case PricingAnnual:
sched = string(stripe.PriceRecurringIntervalYear)
case PricingMonthly:
sched = string(stripe.PriceRecurringIntervalMonth)
}
pl, err := s.GetPlanByID(p.PlanID)
if err != nil {
return fmt.Errorf("plan with id %d not found", p.PlanID)
}
_, err = price.New(&stripe.PriceParams{
Currency: stripe.String(p.Currency),
UnitAmount: stripe.Int64(p.Amount),
Product: stripe.String(pl.ProviderID),
Recurring: &stripe.PriceRecurringParams{
Interval: stripe.String(sched),
TrialPeriodDays: stripe.Int64(int64(p.TrialDays)),
IntervalCount: stripe.Int64(1),
},
})
return err
}
// AddCustomer directly in stripe
func (s *StripeProvider) AddCustomer(c *Customer) error {
_, err := customer.New(&stripe.CustomerParams{
Name: stripe.String(c.Name),
Email: stripe.String(c.Email),
})
return err
}
// Update Customer directly in stripe
func (s *StripeProvider) UpdateCustomer(c *Customer) error {
if c.ProviderID == "" {
return errors.New("missing customer provider id")
}
_, err := customer.Update(c.ProviderID, &stripe.CustomerParams{
Name: stripe.String(c.Name),
Email: stripe.String(c.Email),
})
return err
}
// RemoveCustomer directly in stripe
func (s *StripeProvider) RemoveCustomerByProviderID(providerID string) error {
_, err := customer.Del(providerID, nil)
return err
}
// Verify that the checkout was completed
func (s *StripeProvider) VerifyCheckout(sessionID string) error {
sess, err := session.Get(sessionID, nil)
if err != nil {
return err
}
if sess.PaymentStatus == stripe.CheckoutSessionPaymentStatusUnpaid {
return ErrCheckoutFailed
}
return nil
}
// CheckoutRequest
type CheckoutRequest struct {
CustomerID int64
PriceID int64
RedirectURL string
}
// Checkout returns the url that a user has to visit in order to complete payment
// it registers the customer if it was unavailable
func (s *StripeProvider) Checkout(request *CheckoutRequest) (url string, err error) {
customer, err := s.GetCustomerByID(request.CustomerID)
if err != nil {
return
}
price, err := s.GetPriceByID(request.PriceID)
if err != nil {
return
}
var trialEnd *int64 = nil
if price.TrialDays > 0 {
// we add one day of grace so that stripe displays the correct amount.
// since trial end is calculated from current time, being one second off will result in days -1 being displayed in stripe checkout
trialEnd = stripe.Int64(price.TrialEnd().Add(time.Hour * 24).Unix())
}
params := &stripe.CheckoutSessionParams{
Customer: stripe.String(customer.ProviderID),
SuccessURL: stripe.String(request.RedirectURL),
Mode: stripe.String(string(stripe.CheckoutSessionModeSubscription)),
PaymentMethodCollection: stripe.String("if_required"),
LineItems: []*stripe.CheckoutSessionLineItemParams{
{
Price: stripe.String(price.ProviderID),
Quantity: stripe.Int64(1),
},
},
SubscriptionData: &stripe.CheckoutSessionSubscriptionDataParams{
TrialEnd: trialEnd,
},
}
sess, err := session.New(params)
if err != nil {
return
}
url = sess.URL
return
}
// this should go here
func (StripeProvider) convertPricingSchedule(p *stripe.Price) PricingSchedule {
switch p.Type {
case stripe.PriceTypeOneTime:
return PricingOnce
case stripe.PriceTypeRecurring:
switch p.Recurring.Interval {
case stripe.PriceRecurringIntervalMonth:
return PricingMonthly
case stripe.PriceRecurringIntervalYear:
return PricingAnnual
default:
return ""
}
default:
return ""
}
}