11package subscription
22
33import (
4+ "bytes"
45 "crypto/hmac"
56 "crypto/sha256"
67 "encoding/json"
8+ "fmt"
79 "io"
810 "net/http"
911 "os"
@@ -26,13 +28,282 @@ func ApplyRoutes(r *gin.RouterGroup) {
2628
2729 g .POST ("/helio" , createHelioSubscription )
2830 g .POST ("" , createSubscription )
31+ g .POST ("/apple" , createSubscriptionApple )
2932 g .GET ("/:id" , readSubscription )
3033 g .PATCH ("/:id" , updateSubscription )
3134 g .DELETE ("/:id" , deleteSubscription )
3235 g .GET ("" , readSubscriptions )
3336 }
3437}
3538
39+ func createSubscriptionApple (c * gin.Context ) {
40+ var receipt model.PurchaseRceipt
41+
42+ err := json .NewDecoder (c .Request .Body ).Decode (& receipt )
43+ if err != nil {
44+ c .JSON (http .StatusUnprocessableEntity , gin.H {"error" : err .Error ()})
45+ return
46+ }
47+
48+ // Validate the receipt with Apple
49+ valid , err := validateReceiptApple (receipt .Receipt )
50+ if err != nil || ! valid {
51+ c .JSON (http .StatusForbidden , gin.H {"error" : "Invalid receipt" })
52+ return
53+ }
54+
55+ customer_name := receipt .Name
56+
57+ credits := 0
58+ name := ""
59+ description := ""
60+ devices := 0
61+ networks := 0
62+ members := 0
63+ relays := 0
64+ autoRenew := false
65+ issued := time .Now ()
66+ expires := time .Now ().AddDate (1 , 0 , 0 )
67+
68+ // set the credits, name, and description based on the sku
69+ switch receipt .ProductID {
70+ case "24_hours" :
71+ credits = 1
72+ name = "24 Hours"
73+ description = "Service in any region for 24 hours"
74+ devices = 5
75+ networks = 1
76+ members = 2
77+ relays = 1
78+ autoRenew = false
79+ expires = time .Now ().Add (24 * time .Hour )
80+ case "1_month" :
81+ credits = 1
82+ name = "1 Month"
83+ description = "Service in any region for 1 month"
84+ devices = 5
85+ networks = 1
86+ members = 2
87+ relays = 1
88+ autoRenew = false
89+ expires = time .Now ().AddDate (0 , 1 , 0 )
90+ case "1_week" :
91+ credits = 1
92+ name = "1 Week"
93+ description = "Service in any region for 1 week"
94+ devices = 5
95+ networks = 1
96+ members = 2
97+ relays = 1
98+ autoRenew = false
99+ expires = time .Now ().AddDate (0 , 0 , 7 )
100+ case "basic_monthly" :
101+ fallthrough
102+ case "basic_yearly" :
103+ credits = 1
104+ name = "Basic Service"
105+ description = "A single tunnel or relay in any region"
106+ devices = 5
107+ networks = 1
108+ members = 2
109+ relays = 1
110+ autoRenew = true
111+ case "premium_monthly" :
112+ fallthrough
113+ case "premium_yearly" :
114+ credits = 5
115+ name = "Premium"
116+ description = "Up to 5 tunnels or relays in any region"
117+ devices = 25
118+ networks = 10
119+ members = 5
120+ relays = 5
121+ autoRenew = true
122+ case "professional_monthly" :
123+ fallthrough
124+ case "professional_yearly" :
125+ credits = 10
126+ name = "Professional"
127+ description = "Up to 10 tunnels or relays in any region"
128+ devices = 100
129+ networks = 25
130+ members = 25
131+ relays = 10
132+ autoRenew = true
133+ default :
134+ log .Errorf ("unknown sku %s" , receipt .ProductID )
135+ }
136+
137+ // set the limits based on the sku
138+ accounts , err := core .ReadAllAccounts (receipt .Email )
139+ if err != nil {
140+ log .Error (err )
141+ } else {
142+ // If there's no error and no account, create one.
143+ if len (accounts ) == 0 {
144+ var account model.Account
145+ account .Name = customer_name
146+ account .AccountName = "Company"
147+ account .Email = receipt .Email
148+ account .Role = "Owner"
149+ account .Status = "Active"
150+ account .CreatedBy = receipt .Email
151+ account .UpdatedBy = receipt .Email
152+ account .Picture = "/account-circle.svg"
153+
154+ a , err := core .CreateAccount (& account )
155+ log .Infof ("CREATE ACCOUNT = %v" , a )
156+ if err != nil {
157+ log .Error (err )
158+ }
159+ accounts , err = core .ReadAllAccounts (receipt .Email )
160+ if err != nil {
161+ log .Error (err )
162+ }
163+
164+ }
165+ }
166+
167+ var account * model.Account
168+ for i := 0 ; i < len (accounts ); i ++ {
169+ if accounts [i ].Id == accounts [i ].Parent {
170+ account = accounts [i ]
171+ break
172+ }
173+ }
174+
175+ if account == nil {
176+ log .Errorf ("account not found for email %s" , receipt .Email )
177+ c .JSON (http .StatusNotFound , gin.H {"error" : "Account not found" })
178+ return
179+ }
180+
181+ limits , err := core .ReadLimits (account .Id )
182+ if err != nil {
183+ log .Error (err )
184+ limits_id , err := util .GenerateRandomString (8 )
185+ if err != nil {
186+ log .Error (err )
187+ }
188+ limits_id = "limits-" + limits_id
189+
190+ limits = & model.Limits {
191+ Id : limits_id ,
192+ AccountID : account .Id ,
193+ MaxDevices : 0 ,
194+ MaxNetworks : 0 ,
195+ MaxMembers : 0 ,
196+ MaxServices : 0 ,
197+ Tolerance : core .GetDefaultTolerance (),
198+ CreatedBy : receipt .Email ,
199+ UpdatedBy : receipt .Email ,
200+ Created : time .Now (),
201+ Updated : time .Now (),
202+ }
203+ }
204+
205+ limits .MaxDevices += devices
206+ limits .MaxNetworks += networks
207+ limits .MaxMembers += members
208+ limits .MaxServices += relays
209+
210+ errs := limits .IsValid ()
211+ if len (errs ) != 0 {
212+ for _ , err := range errs {
213+ log .WithFields (log.Fields {
214+ "err" : err ,
215+ }).Error ("limits validation error" )
216+ }
217+ c .JSON (http .StatusBadRequest , gin.H {"error" : "Limits validation error" })
218+ return
219+ }
220+
221+ // save limits to mongodb
222+ mongo .Serialize (limits .Id , "id" , "limits" , limits )
223+
224+ // generate a random subscription id
225+ id , err := util .RandomString (8 )
226+ if err != nil {
227+ log .Error (err )
228+ }
229+ id = receipt .Source + "-" + id
230+
231+ // construct a subscription object
232+ lu := time .Now ()
233+ subscription := model.Subscription {
234+ Id : id ,
235+ AccountID : account .Id ,
236+ Email : receipt .Email ,
237+ Name : name ,
238+ Description : description ,
239+ Issued : & issued ,
240+ LastUpdated : & lu ,
241+ Expires : & expires ,
242+ Credits : credits ,
243+ Sku : receipt .ProductID ,
244+ Status : "active" ,
245+ AutoRenew : autoRenew ,
246+ Receipt : receipt .Receipt ,
247+ }
248+
249+ errs = subscription .IsValid ()
250+ if len (errs ) != 0 {
251+ for _ , err := range errs {
252+ log .WithFields (log.Fields {
253+ "err" : err ,
254+ }).Error ("subscription validation error" )
255+ }
256+ return
257+ }
258+
259+ // save subscription to mongodb
260+ mongo .Serialize (subscription .Id , "id" , "subscriptions" , subscription )
261+
262+ c .JSON (http .StatusOK , subscription )
263+ return
264+
265+ }
266+
267+ func validateReceiptApple (receipt string ) (bool , error ) {
268+ // Apple receipt validation URL
269+ url := "https://buy.itunes.apple.com/verifyReceipt"
270+
271+ // Create the request payload
272+ payload := map [string ]string {
273+ "receipt-data" : receipt ,
274+ "password" : os .Getenv ("APPLE_ITUNES_SHARED_SECRET" ), // Replace with your app's shared secret
275+ }
276+ payloadBytes , err := json .Marshal (payload )
277+ if err != nil {
278+ return false , err
279+ }
280+
281+ // Send the request to Apple
282+ resp , err := http .Post (url , "application/json" , bytes .NewBuffer (payloadBytes ))
283+ if err != nil {
284+ return false , err
285+ }
286+ defer resp .Body .Close ()
287+
288+ if resp .StatusCode != http .StatusOK {
289+ return false , fmt .Errorf ("invalid response from Apple: %s" , resp .Status )
290+ }
291+
292+ // Parse the response to check the receipt status
293+ var result map [string ]interface {}
294+ err = json .NewDecoder (resp .Body ).Decode (& result )
295+ if err != nil {
296+ return false , err
297+ }
298+
299+ // Check if the receipt is valid
300+ if status , ok := result ["status" ].(float64 ); ok && status == 0 {
301+ return true , nil
302+ }
303+
304+ return false , nil
305+ }
306+
36307func createHelioSubscription (c * gin.Context ) {
37308 var body string
38309 var sub map [string ]interface {}
0 commit comments