diff --git a/go.mod b/go.mod index f7e3f3cd..f3b28005 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/slack-go/slack v0.11.2 github.com/spf13/cobra v1.5.0 github.com/spf13/viper v1.12.0 - github.com/stripe/stripe-go/v72 v72.122.0 github.com/stripe/stripe-go/v73 v73.0.1 gorm.io/driver/mysql v1.3.5 gorm.io/gorm v1.23.8 diff --git a/pkg/api/api.go b/pkg/api/api.go index c2109b0c..cbc2d268 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -85,7 +85,6 @@ func AdminRestAPI() { v1.DELETE("/group", group.DeleteByAdmin) // Group Update v1.PUT("/group/:id", group.UpdateByAdmin) - v1.DELETE("/group/:id/subscription", group.CancelSubscription) v1.GET("/group", group.GetAllByAdmin) v1.GET("/group/:id", group.GetByAdmin) @@ -168,7 +167,8 @@ func AdminRestAPI() { // // Payment // - //v1.POST("/group/:id/service", service.AddByAdmin) + v1.POST("/group/:id/payment/subscribe", payment.PostAdminSubscribeGettingURL) + v1.GET("/group/:id/payment", payment.GetAdminBillingPortalURL) // Delete v1.DELETE("/payment/:id", payment.DeleteByAdmin) v1.POST("/payment/:id/refund", payment.RefundByAdmin) diff --git a/pkg/api/core/constant.go b/pkg/api/core/constant.go index 35c121af..4613a1d6 100644 --- a/pkg/api/core/constant.go +++ b/pkg/api/core/constant.go @@ -5,6 +5,19 @@ package core // // Membership +// 1-49 一般支払い +// 70-89 特別枠(無料) +// 90-99 特別枠(運営) +// 1: 一般会員 +// 70: 学生会員 +// 90: 運営委員 +// 99: "" +var MemberTypes = []ConstantMembership{ + MemberTypeStandard, + MemberTypeStudent, + MemberTypeCommittee, + MemberTypeDisable, +} var MemberTypeStandard = ConstantMembership{ID: 1, Name: "一般会員"} var MemberTypeStudent = ConstantMembership{ID: 70, Name: "学生会員"} var MemberTypeCommittee = ConstantMembership{ID: 90, Name: "運営委員"} diff --git a/pkg/api/core/get.go b/pkg/api/core/get.go new file mode 100644 index 00000000..5f4367f4 --- /dev/null +++ b/pkg/api/core/get.go @@ -0,0 +1,12 @@ +package core + +import "fmt" + +func GetMembershipTypeID(id uint) (ConstantMembership, error) { + for _, memberType := range MemberTypes { + if memberType.ID == id { + return memberType, nil + } + } + return ConstantMembership{}, fmt.Errorf("error: getting membership") +} diff --git a/pkg/api/core/group/info/interface.go b/pkg/api/core/group/info/interface.go index 6a669aff..a35088c5 100644 --- a/pkg/api/core/group/info/interface.go +++ b/pkg/api/core/group/info/interface.go @@ -17,27 +17,26 @@ type User struct { } type Group struct { - ID uint `json:"id"` - PaymentMembershipTemplate string `json:"payment_membership_template"` - Agree *bool `json:"agree"` - Question string `json:"question"` - Org string `json:"org"` - OrgEn string `json:"org_en"` - PostCode string `json:"postcode"` - Address string `json:"address"` - AddressEn string `json:"address_en"` - Tel string `json:"tel"` - Country string `json:"country"` - Contract string `json:"contract"` - Paid *bool `json:"paid"` - MemberInfo string `json:"member_info"` - MemberExpired *time.Time `json:"member_expired"` - Student *bool `json:"student"` - StudentExpired *time.Time `json:"student_expired"` - Pass *bool `json:"pass"` - Lock *bool `json:"lock"` - ExpiredStatus *uint `json:"expired_status"` - AddAllow *bool `json:"add_allow"` + ID uint `json:"id"` + Agree *bool `json:"agree"` + Question string `json:"question"` + Org string `json:"org"` + OrgEn string `json:"org_en"` + PostCode string `json:"postcode"` + Address string `json:"address"` + AddressEn string `json:"address_en"` + Tel string `json:"tel"` + Country string `json:"country"` + Contract string `json:"contract"` + CouponID string `json:"coupon_id"` + MemberTypeID uint `json:"member_type_id"` + MemberType string `json:"member_type"` + MemberExpired *time.Time `json:"member_expired"` + IsExpired bool `json:"is_expired"` + IsStripeID bool `json:"is_stripe_id"` + Pass *bool `json:"pass"` + ExpiredStatus *uint `json:"expired_status"` + AddAllow *bool `json:"add_allow"` } type Notice struct { diff --git a/pkg/api/core/group/info/v0/info.go b/pkg/api/core/group/info/v0/info.go index b42d4ee3..503e9d39 100644 --- a/pkg/api/core/group/info/v0/info.go +++ b/pkg/api/core/group/info/v0/info.go @@ -16,6 +16,7 @@ import ( "net/http" "sort" "strconv" + "time" ) func Get(c *gin.Context) { @@ -60,19 +61,50 @@ func Get(c *gin.Context) { if authResult.User.GroupID != nil { // Membership Info - membershipInfo := "一般会員" - membershipPlan := "未設定" - paid := false + membership, err := core.GetMembershipTypeID(authResult.User.Group.MemberType) + if err != nil { + c.JSON(http.StatusInternalServerError, common.Error{Error: err.Error()}) + return + } + + // isExpired(課金確認) + isExpired := false + if authResult.User.Group.MemberType < 50 && authResult.User.Group.MemberExpired != nil { + jst, err := time.LoadLocation("Asia/Tokyo") + if err != nil { + panic(err) + } + nowJST := time.Now().In(jst) + if nowJST.Unix() > authResult.User.Group.MemberExpired.Add(time.Hour*24).Unix() { + isExpired = true + } + } else if authResult.User.Group.MemberType < 50 && authResult.User.Group.MemberExpired == nil { + isExpired = true + } + + // isStripeID + isStripeID := true + if authResult.User.Group.StripeCustomerID == nil || *authResult.User.Group.StripeCustomerID == "" || + authResult.User.Group.StripeSubscriptionID == nil || *authResult.User.Group.StripeSubscriptionID == "" { + isStripeID = false + } + + // coupon + couponID := "" + if authResult.User.Group.CouponID != nil { + couponID = *authResult.User.Group.CouponID + } resultGroup = info.Group{ - ID: authResult.User.Group.ID, - Student: &[]bool{authResult.User.Group.MemberType == core.MemberTypeStudent.ID}[0], - Pass: authResult.User.Group.Pass, - ExpiredStatus: authResult.User.Group.ExpiredStatus, - MemberInfo: membershipInfo, - Paid: &paid, - PaymentMembershipTemplate: membershipPlan, - MemberExpired: authResult.User.Group.MemberExpired, + ID: authResult.User.Group.ID, + Pass: authResult.User.Group.Pass, + ExpiredStatus: authResult.User.Group.ExpiredStatus, + IsExpired: isExpired, + IsStripeID: isStripeID, + MemberTypeID: membership.ID, + MemberType: membership.Name, + MemberExpired: authResult.User.Group.MemberExpired, + CouponID: couponID, } if authResult.User.Level < 3 { resultGroup.Agree = dbUserResult.User[0].Group.Agree diff --git a/pkg/api/core/group/v0/admin.go b/pkg/api/core/group/v0/admin.go index 7547079e..3fee1520 100644 --- a/pkg/api/core/group/v0/admin.go +++ b/pkg/api/core/group/v0/admin.go @@ -7,10 +7,7 @@ import ( auth "github.com/homenoc/dsbd-backend/pkg/api/core/auth/v0" "github.com/homenoc/dsbd-backend/pkg/api/core/common" "github.com/homenoc/dsbd-backend/pkg/api/core/group" - "github.com/homenoc/dsbd-backend/pkg/api/core/tool/config" dbGroup "github.com/homenoc/dsbd-backend/pkg/api/store/group/v0" - "github.com/stripe/stripe-go/v72" - "github.com/stripe/stripe-go/v72/sub" "gorm.io/gorm" "log" "net/http" @@ -142,57 +139,3 @@ func GetAllByAdmin(c *gin.Context) { c.JSON(http.StatusOK, group.ResultAdminAll{Group: result.Group}) } } - -func CancelSubscription(c *gin.Context) { - // ID取得 - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusBadRequest, common.Error{Error: err.Error()}) - return - } - - // serviceIDが0の時エラー処理 - if id == 0 { - c.JSON(http.StatusBadRequest, common.Error{Error: fmt.Sprintf("This id is wrong... ")}) - return - } - - resultAdmin := auth.AdminAuthorization(c.Request.Header.Get("ACCESS_TOKEN")) - if resultAdmin.Err != nil { - c.JSON(http.StatusUnauthorized, common.Error{Error: resultAdmin.Err.Error()}) - return - } - - resultGroup := dbGroup.Get(group.ID, &core.Group{Model: gorm.Model{ID: uint(id)}}) - if resultGroup.Err != nil { - c.JSON(http.StatusInternalServerError, common.Error{Error: resultGroup.Err.Error()}) - return - } - - if resultGroup.Group[0].StripeSubscriptionID == nil { - c.JSON(http.StatusBadRequest, common.Error{Error: "Subscription ID is not exists."}) - return - } - - noticeCancelSubscriptionByAdmin(resultGroup.Group[0]) - - stripe.Key = config.Conf.Stripe.SecretKey - - _, err = sub.Cancel(*resultGroup.Group[0].StripeSubscriptionID, nil) - if err != nil { - log.Printf("pi.New: %v", err) - c.JSON(http.StatusInternalServerError, common.Error{Error: err.Error()}) - return - } - - err = dbGroup.Update(group.UpdateAll, core.Group{ - StripeSubscriptionID: &[]string{""}[0], - }) - if err != nil { - log.Printf("Error: %v", err) - c.JSON(http.StatusInternalServerError, common.Error{Error: err.Error()}) - return - } - - c.JSON(http.StatusOK, common.Result{}) -} diff --git a/pkg/api/core/group/v0/group.go b/pkg/api/core/group/v0/group.go index a77be51c..d9dd4349 100644 --- a/pkg/api/core/group/v0/group.go +++ b/pkg/api/core/group/v0/group.go @@ -6,12 +6,9 @@ import ( auth "github.com/homenoc/dsbd-backend/pkg/api/core/auth/v0" "github.com/homenoc/dsbd-backend/pkg/api/core/common" "github.com/homenoc/dsbd-backend/pkg/api/core/group" - "github.com/homenoc/dsbd-backend/pkg/api/core/tool/config" "github.com/homenoc/dsbd-backend/pkg/api/core/user" dbGroup "github.com/homenoc/dsbd-backend/pkg/api/store/group/v0" dbUser "github.com/homenoc/dsbd-backend/pkg/api/store/user/v0" - "github.com/stripe/stripe-go/v72" - "github.com/stripe/stripe-go/v72/customer" "gorm.io/gorm" "log" "net/http" @@ -72,34 +69,22 @@ func Add(c *gin.Context) { memberType = core.MemberTypeStudent.ID } - // added customer (stripe) - stripe.Key = config.Conf.Stripe.SecretKey - - params := &stripe.CustomerParams{ - Description: stripe.String("Org: " + input.Org + "(" + input.OrgEn + ")"), - } - cus, err := customer.New(params) - if err != nil { - log.Println("Error: " + err.Error()) - } - groupData := core.Group{ - Agree: &[]bool{*input.Agree}[0], - StripeCustomerID: &cus.ID, - Question: input.Question, - Org: input.Org, - OrgEn: input.OrgEn, - PostCode: input.PostCode, - Address: input.Address, - AddressEn: input.AddressEn, - Tel: input.Tel, - Country: input.Country, - ExpiredStatus: &[]uint{0}[0], - Contract: input.Contract, - MemberType: memberType, - MemberExpired: memberExpired, - Pass: &[]bool{false}[0], - AddAllow: &[]bool{true}[0], + Agree: &[]bool{*input.Agree}[0], + Question: input.Question, + Org: input.Org, + OrgEn: input.OrgEn, + PostCode: input.PostCode, + Address: input.Address, + AddressEn: input.AddressEn, + Tel: input.Tel, + Country: input.Country, + ExpiredStatus: &[]uint{0}[0], + Contract: input.Contract, + MemberType: memberType, + MemberExpired: memberExpired, + Pass: &[]bool{false}[0], + AddAllow: &[]bool{true}[0], } _, err = dbGroup.Create(&groupData) diff --git a/pkg/api/core/payment/v0/admin.go b/pkg/api/core/payment/v0/admin.go index 1558a189..4f2000fc 100644 --- a/pkg/api/core/payment/v0/admin.go +++ b/pkg/api/core/payment/v0/admin.go @@ -1,81 +1,170 @@ package v0 import ( + "fmt" "github.com/gin-gonic/gin" "github.com/homenoc/dsbd-backend/pkg/api/core" auth "github.com/homenoc/dsbd-backend/pkg/api/core/auth/v0" "github.com/homenoc/dsbd-backend/pkg/api/core/common" + "github.com/homenoc/dsbd-backend/pkg/api/core/group" "github.com/homenoc/dsbd-backend/pkg/api/core/notice" "github.com/homenoc/dsbd-backend/pkg/api/core/payment" "github.com/homenoc/dsbd-backend/pkg/api/core/tool/config" + dbGroup "github.com/homenoc/dsbd-backend/pkg/api/store/group/v0" dbPayment "github.com/homenoc/dsbd-backend/pkg/api/store/payment/v0" "github.com/stripe/stripe-go/v73" + billingSession "github.com/stripe/stripe-go/v73/billingportal/session" + "github.com/stripe/stripe-go/v73/checkout/session" + "github.com/stripe/stripe-go/v73/customer" "github.com/stripe/stripe-go/v73/refund" "gorm.io/gorm" + "log" "net/http" "strconv" + "time" ) -func AddByAdmin(c *gin.Context) { - //var input notice.Input - // - //resultAdmin := auth.AdminAuthorization(c.Request.Header.Get("ACCESS_TOKEN")) - //if resultAdmin.Err != nil { - // c.JSON(http.StatusUnauthorized, common.Error{Error: resultAdmin.Err.Error()}) - // return - //} - //err := c.BindJSON(&input) - //if err != nil { - // log.Println(err) - // c.JSON(http.StatusBadRequest, common.Error{Error: err.Error()}) - // return - //} - // - //if err = check(input); err != nil { - // c.JSON(http.StatusBadRequest, common.Error{Error: err.Error()}) - // return - //} - // - //// 時間はJST基準 - //jst, _ := time.LoadLocation(config.Conf.Controller.TimeZone) - // - //// 9999年12月31日 23:59:59.59 - //var endTime = time.Date(9999, time.December, 31, 23, 59, 59, 59, jst) - // - //startTime, _ := time.ParseInLocation("2006-01-02 15:04:05", input.StartTime, jst) - //if input.EndTime != nil { - // endTime, _ = time.ParseInLocation("2006-01-02 15:04:05", *input.EndTime, jst) - //} - // - //var userIDArray []uint - // - //for _, tmpID := range userExtraction(input.UserID, input.GroupID, input.NOCID) { - // userIDArray = append(userIDArray, tmpID) - //} - // - //resultUser := dbUser.GetArray(userIDArray) - //if resultUser.Err != nil { - // log.Println(resultUser.Err.Error()) - // c.JSON(http.StatusInternalServerError, common.Error{Error: resultUser.Err.Error()}) - // return - //} - // - //if _, err = dbNotice.Create(&core.Notice{ - // User: resultUser.User, - // Everyone: input.Everyone, - // StartTime: startTime, - // EndTime: endTime, - // Important: input.Important, - // Fault: input.Fault, - // Info: input.Info, - // Title: input.Title, - // Data: input.Data, - //}); err != nil { - // c.JSON(http.StatusInternalServerError, common.Error{Error: err.Error()}) - // return - //} - //noticeSlackAddByAdmin(input) - //c.JSON(http.StatusOK, notice.Result{}) +func PostAdminSubscribeGettingURL(c *gin.Context) { + // ID取得 + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, common.Error{Error: err.Error()}) + return + } + + // Admin authentication + resultAdmin := auth.AdminAuthorization(c.Request.Header.Get("ACCESS_TOKEN")) + if resultAdmin.Err != nil { + c.JSON(http.StatusUnauthorized, common.Error{Error: resultAdmin.Err.Error()}) + return + } + + // serviceIDが0の時エラー処理 + if id == 0 { + c.JSON(http.StatusBadRequest, common.Error{Error: fmt.Sprintf("ID is wrong... ")}) + return + } + + result := dbGroup.Get(group.ID, &core.Group{Model: gorm.Model{ID: uint(id)}}) + if result.Err != nil { + c.JSON(http.StatusInternalServerError, common.Error{Error: result.Err.Error()}) + return + } + + var input payment.Input + err = c.BindJSON(&input) + if err != nil { + c.JSON(http.StatusBadRequest, common.Error{Error: err.Error()}) + return + } + + // search plan + membershipWithTemplate, err := config.GetMembershipTemplate(input.Plan) + if err != nil { + c.JSON(http.StatusBadRequest, common.Error{Error: "invalid plan"}) + return + } + + // exist check: stripeCustomerID + if *result.Group[0].StripeCustomerID == "" || result.Group[0].StripeCustomerID == nil { + params := &stripe.CustomerParams{ + Description: stripe.String("[" + strconv.Itoa(int(result.Group[0].ID)) + "] Org: " + result.Group[0].Org + "(" + result.Group[0].OrgEn + ")"), + } + cus, err := customer.New(params) + if err != nil { + noticePaymentError(true, []string{"Type: Create Customer", "Error: " + err.Error()}) + log.Println("Error: " + err.Error()) + } + err = dbGroup.Update(group.UpdateAll, core.Group{Model: gorm.Model{ID: result.Group[0].ID}, StripeCustomerID: &cus.ID}) + noticePaymentLog(stripe.Event{ + ID: cus.ID, + Type: "stripe customer追加", + }) + result.Group[0].StripeCustomerID = &cus.ID + } + + date := time.Now() + params := &stripe.CheckoutSessionParams{ + Mode: stripe.String(string(stripe.CheckoutSessionModeSubscription)), + Customer: result.Group[0].StripeCustomerID, + LineItems: []*stripe.CheckoutSessionLineItemParams{ + { + Price: stripe.String(membershipWithTemplate.PriceID), + Quantity: stripe.Int64(1), + }, + }, + SuccessURL: stripe.String(config.Conf.Controller.Admin.Url), + CancelURL: stripe.String(config.Conf.Controller.Admin.Url), + ExpiresAt: stripe.Int64(date.Add(time.Minute * 30).Unix()), + SubscriptionData: &stripe.CheckoutSessionSubscriptionDataParams{ + Metadata: map[string]string{ + "type": "membership", + "name": "---", + "group_id": strconv.Itoa(int(result.Group[0].ID)), + "log": "[---] admin" + + "_[" + strconv.Itoa(int(result.Group[0].ID)) + "] " + result.Group[0].Org, + }, + }, + } + + s, err := session.New(params) + + if err != nil { + noticePaymentError(true, []string{"Type: Subscribe Session", "Error: " + err.Error()}) + c.JSON(http.StatusInternalServerError, common.Error{Error: err.Error()}) + return + } + + c.JSON(http.StatusOK, map[string]string{"url": s.URL}) +} + +func GetAdminBillingPortalURL(c *gin.Context) { + // ID取得 + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, common.Error{Error: err.Error()}) + return + } + + // Admin authentication + resultAdmin := auth.AdminAuthorization(c.Request.Header.Get("ACCESS_TOKEN")) + if resultAdmin.Err != nil { + c.JSON(http.StatusUnauthorized, common.Error{Error: resultAdmin.Err.Error()}) + return + } + + // serviceIDが0の時エラー処理 + if id == 0 { + c.JSON(http.StatusBadRequest, common.Error{Error: fmt.Sprintf("ID is wrong... ")}) + return + } + + result := dbGroup.Get(group.ID, &core.Group{Model: gorm.Model{ID: uint(id)}}) + if result.Err != nil { + c.JSON(http.StatusInternalServerError, common.Error{Error: result.Err.Error()}) + return + } + + // exist check: stripeCustomerID + if *result.Group[0].StripeCustomerID == "" || result.Group[0].StripeCustomerID == nil { + c.JSON(http.StatusNotFound, common.Error{Error: "CustomerID is not found..."}) + return + } + + params := &stripe.BillingPortalSessionParams{ + Customer: stripe.String(*result.Group[0].StripeCustomerID), + Configuration: stripe.String(config.Conf.Stripe.MembershipConfiguration), + ReturnURL: stripe.String(config.Conf.Controller.Admin.Url), + } + + s, err := billingSession.New(params) + if err != nil { + noticePaymentError(true, []string{"Type: BillingSession", "Error: " + err.Error()}) + c.JSON(http.StatusInternalServerError, common.Error{Error: err.Error()}) + return + } + + c.JSON(http.StatusOK, map[string]string{"url": s.URL}) } func DeleteByAdmin(c *gin.Context) { @@ -151,8 +240,6 @@ func RefundByAdmin(c *gin.Context) { return } - stripe.Key = config.Conf.Stripe.SecretKey - _, err = refund.New(&stripe.RefundParams{ PaymentIntent: stripe.String(result[0].PaymentIntentID), }) diff --git a/pkg/api/core/payment/v0/payment.go b/pkg/api/core/payment/v0/payment.go index fe27b610..e16dd11b 100644 --- a/pkg/api/core/payment/v0/payment.go +++ b/pkg/api/core/payment/v0/payment.go @@ -5,11 +5,16 @@ import ( "github.com/homenoc/dsbd-backend/pkg/api/core" auth "github.com/homenoc/dsbd-backend/pkg/api/core/auth/v0" "github.com/homenoc/dsbd-backend/pkg/api/core/common" + "github.com/homenoc/dsbd-backend/pkg/api/core/group" "github.com/homenoc/dsbd-backend/pkg/api/core/payment" "github.com/homenoc/dsbd-backend/pkg/api/core/tool/config" + dbGroup "github.com/homenoc/dsbd-backend/pkg/api/store/group/v0" "github.com/stripe/stripe-go/v73" billingSession "github.com/stripe/stripe-go/v73/billingportal/session" "github.com/stripe/stripe-go/v73/checkout/session" + "github.com/stripe/stripe-go/v73/customer" + "gorm.io/gorm" + "log" "net/http" "strconv" "time" @@ -40,15 +45,25 @@ func PostSubscribeGettingURL(c *gin.Context) { } // exist check: stripeCustomerID - if *resultAuth.User.Group.StripeCustomerID == "" { - c.JSON(http.StatusInternalServerError, common.Error{Error: "stripe customer id not found..."}) - return - } - - // exist check: stripeCustomerID - if *resultAuth.User.Group.StripeCustomerID == "" { - c.JSON(http.StatusInternalServerError, common.Error{Error: "stripe customer id not found..."}) - return + if *resultAuth.User.Group.StripeCustomerID == "" || resultAuth.User.Group.StripeCustomerID == nil { + params := &stripe.CustomerParams{ + Description: stripe.String("[" + strconv.Itoa(int(resultAuth.User.Group.ID)) + "] Org: " + resultAuth.User.Group.Org + "(" + resultAuth.User.Group.OrgEn + ")"), + } + cus, err := customer.New(params) + if err != nil { + noticePaymentError(false, []string{ + "User: [" + strconv.Itoa(int(resultAuth.User.ID)) + "] " + resultAuth.User.Name, + "Group: [" + strconv.Itoa(int(resultAuth.User.Group.ID)) + "] " + resultAuth.User.Group.Org, + "Type: Create Customer", "Error: " + err.Error()}, + ) + log.Println("Error: " + err.Error()) + } + err = dbGroup.Update(group.UpdateAll, core.Group{Model: gorm.Model{ID: resultAuth.User.Group.ID}, StripeCustomerID: &cus.ID}) + noticePaymentLog(stripe.Event{ + ID: cus.ID, + Type: "stripe customer追加", + }) + resultAuth.User.Group.StripeCustomerID = &cus.ID } date := time.Now() @@ -62,13 +77,13 @@ func PostSubscribeGettingURL(c *gin.Context) { }, }, SuccessURL: stripe.String(config.Conf.Controller.User.Url), - //CancelURL: stripe.String("https://example.com/cancel"), - ExpiresAt: stripe.Int64(date.Add(time.Minute * 30).Unix()), + CancelURL: stripe.String(config.Conf.Controller.User.Url), + ExpiresAt: stripe.Int64(date.Add(time.Minute * 30).Unix()), SubscriptionData: &stripe.CheckoutSessionSubscriptionDataParams{ Metadata: map[string]string{ "type": "membership", "group_id": strconv.Itoa(int(resultAuth.User.Group.ID)), - "name": "Yuto Yoneda", + "name": strconv.Itoa(int(resultAuth.User.ID)), "log": "[" + strconv.Itoa(int(resultAuth.User.ID)) + "] " + resultAuth.User.Name + "_[" + strconv.Itoa(int(resultAuth.User.Group.ID)) + "] " + resultAuth.User.Group.Org, }, @@ -95,8 +110,14 @@ func GetBillingPortalURL(c *gin.Context) { return } + // exist check: stripeCustomerID + if *resultAuth.User.Group.StripeCustomerID == "" || resultAuth.User.Group.StripeCustomerID == nil { + c.JSON(http.StatusNotFound, common.Error{Error: "CustomerID is not found..."}) + return + } + params := &stripe.BillingPortalSessionParams{ - Customer: stripe.String("cus_MBBRylUHxUQvVc"), + Customer: stripe.String(*resultAuth.User.Group.StripeCustomerID), Configuration: stripe.String(config.Conf.Stripe.MembershipConfiguration), ReturnURL: stripe.String(config.Conf.Controller.User.Url), } diff --git a/pkg/api/core/payment/v0/slack.go b/pkg/api/core/payment/v0/slack.go index 8672ba19..2159c982 100644 --- a/pkg/api/core/payment/v0/slack.go +++ b/pkg/api/core/payment/v0/slack.go @@ -37,3 +37,25 @@ func noticePayment(baseKeyValue []string) { }, )) } + +func noticePaymentError(isAdmin bool, baseKeyValue []string) { + var title = "エラー" + if isAdmin { + title += " (管理者)" + } else { + title += " (ユーザ)" + } + var slackAttachField []slack.AttachmentField + for _, keyValue := range baseKeyValue { + splitKeyValue := strings.Split(keyValue, ":") + slackAttachField = append(slackAttachField, slack.AttachmentField{Title: splitKeyValue[0], Value: keyValue[len(splitKeyValue[0])+1:]}) + } + + notification.Notification.Slack.PostMessage(config.Conf.Slack.Channels.Payment, slack.MsgOptionAttachments( + slack.Attachment{ + Color: "danger", + Title: title, + Fields: slackAttachField, + }, + )) +} diff --git a/pkg/api/core/payment/v0/websocket.go b/pkg/api/core/payment/v0/websocket.go index 900fd2b7..a836c5a7 100644 --- a/pkg/api/core/payment/v0/websocket.go +++ b/pkg/api/core/payment/v0/websocket.go @@ -6,10 +6,13 @@ import ( "github.com/gin-gonic/gin" "github.com/homenoc/dsbd-backend/pkg/api/core" "github.com/homenoc/dsbd-backend/pkg/api/core/common" + "github.com/homenoc/dsbd-backend/pkg/api/core/group" "github.com/homenoc/dsbd-backend/pkg/api/core/tool/config" + dbGroup "github.com/homenoc/dsbd-backend/pkg/api/store/group/v0" dbPayment "github.com/homenoc/dsbd-backend/pkg/api/store/payment/v0" "github.com/stripe/stripe-go/v73" "github.com/stripe/stripe-go/v73/webhook" + "gorm.io/gorm" "io/ioutil" "log" "net/http" @@ -19,8 +22,6 @@ import ( ) func GetStripeWebHook(c *gin.Context) { - stripe.Key = config.Conf.Stripe.SecretKey - const MaxBodyBytes = int64(65536) body := http.MaxBytesReader(c.Writer, c.Request.Body, MaxBodyBytes) @@ -32,7 +33,6 @@ func GetStripeWebHook(c *gin.Context) { event := stripe.Event{} if err := json.Unmarshal(payload, &event); err != nil { - fmt.Fprintf(os.Stderr, "⚠️ Webhook error while parsing basic request. %v\n", err.Error()) c.JSON(http.StatusBadRequest, err.Error()) return } @@ -60,8 +60,9 @@ func GetStripeWebHook(c *gin.Context) { } dataType := event.Data.Object["metadata"].(map[string]interface{})["type"].(string) name := event.Data.Object["metadata"].(map[string]interface{})["name"].(string) - groupID := event.Data.Object["metadata"].(map[string]interface{})["group_id"].(string) - etc := "GroupID: " + groupID + ", UserName: " + name + groupIDStr := event.Data.Object["metadata"].(map[string]interface{})["group_id"].(string) + etc := "GroupID: " + groupIDStr + ", UserName: " + name + groupID, _ := strconv.Atoi(groupIDStr) // stripe standard data amountTotal := event.Data.Object["amount_total"].(float64) @@ -76,7 +77,14 @@ func GetStripeWebHook(c *gin.Context) { }) etc += "UserName: " + name } else if dataType == "membership" { - etc += "GroupID: " + groupID + dbPayment.Create(&core.Payment{ + Type: core.PaymentMembership, + GroupID: &[]uint{uint(groupID)}[0], + Refund: &[]bool{false}[0], + PaymentIntentID: paymentIntent, + Fee: uint(amountTotal), + }) + etc += "GroupID: " + groupIDStr break } @@ -91,68 +99,88 @@ func GetStripeWebHook(c *gin.Context) { noticePayment(field) case "customer.subscription.created": // meta - var dataType, etc string + var dataType, groupIDStr, etc string + var groupID int _, ok := event.Data.Object["metadata"].(map[string]interface{})["type"] if !ok { break } else { dataType = event.Data.Object["metadata"].(map[string]interface{})["type"].(string) name := event.Data.Object["metadata"].(map[string]interface{})["name"].(string) - groupID := event.Data.Object["metadata"].(map[string]interface{})["group_id"].(string) - etc = "GroupID: " + groupID + ", UserName: " + name + groupIDStr = event.Data.Object["metadata"].(map[string]interface{})["group_id"].(string) + etc = "GroupID: " + groupIDStr + ", UserName: " + name } - // stripe standard data - customer := event.Data.Object["customer"].(string) - planID := event.Data.Object["plan"].(map[string]interface{})["id"].(string) - amount := event.Data.Object["plan"].(map[string]interface{})["amount"].(float64) - interval := event.Data.Object["plan"].(map[string]interface{})["interval"].(string) - periodStart := event.Data.Object["current_period_start"].(float64) - periodEnd := event.Data.Object["current_period_end"].(float64) - periodStartTime := time.Unix(int64(periodStart), 0) - periodEndTime := time.Unix(int64(periodEnd), 0) - - // slack notify(payment log) - field := []string{ - "Type:" + dataType + "(" + event.Type + ")", - "ID:" + event.ID, - "CustomerID:" + customer, - "PlanID:" + planID, - "Start-EndDate:" + fmt.Sprintf(periodStartTime.Format("2006-01-02")+" - "+periodEndTime.Format("2006-01-02")), - "Etc:" + etc, - "Fee:" + strconv.Itoa(int(uint(amount))) + " 円 (" + interval + ")", + if dataType == "membership" { + groupID, _ = strconv.Atoi(groupIDStr) + + // stripe standard data + customer := event.Data.Object["customer"].(string) + sub := event.Data.Object["id"].(string) + planID := event.Data.Object["plan"].(map[string]interface{})["id"].(string) + amount := event.Data.Object["plan"].(map[string]interface{})["amount"].(float64) + interval := event.Data.Object["plan"].(map[string]interface{})["interval"].(string) + periodStart := event.Data.Object["current_period_start"].(float64) + periodEnd := event.Data.Object["current_period_end"].(float64) + periodStartTime := time.Unix(int64(periodStart), 0) + periodEndTime := time.Unix(int64(periodEnd), 0) + jst, _ := time.LoadLocation(config.Conf.Controller.TimeZone) + timeDate := time.Date(periodEndTime.Year(), periodEndTime.Month(), periodEndTime.Day(), 0, 0, 0, 0, jst) + if groupID != 0 { + err = dbGroup.Update(group.UpdateAll, core.Group{Model: gorm.Model{ID: uint(groupID)}, StripeSubscriptionID: &sub, MemberExpired: &timeDate}) + } + // slack notify(payment log) + field := []string{ + "Type:" + dataType + "(" + event.Type + ")", + "ID:" + event.ID, + "CustomerID:" + customer, + "PlanID:" + planID, + "Start-EndDate:" + fmt.Sprintf(periodStartTime.Format("2006-01-02")+" - "+periodEndTime.Format("2006-01-02")), + "Etc:" + etc, + "Fee:" + strconv.Itoa(int(uint(amount))) + " 円 (" + interval + ")", + } + noticePayment(field) } - noticePayment(field) case "customer.subscription.updated": + var groupID int + // meta dataType := event.Data.Object["metadata"].(map[string]interface{})["type"].(string) name := event.Data.Object["metadata"].(map[string]interface{})["name"].(string) - groupID := event.Data.Object["metadata"].(map[string]interface{})["group_id"].(string) - etc := "GroupID: " + groupID + ", UserName: " + name - - // stripe standard data - customer := event.Data.Object["customer"].(string) - planID := event.Data.Object["plan"].(map[string]interface{})["id"].(string) - amount := event.Data.Object["plan"].(map[string]interface{})["amount"].(float64) - interval := event.Data.Object["plan"].(map[string]interface{})["interval"].(string) - periodStart := event.Data.Object["current_period_start"].(float64) - periodEnd := event.Data.Object["current_period_end"].(float64) - periodStartTime := time.Unix(int64(periodStart), 0) - periodEndTime := time.Unix(int64(periodEnd), 0) - status := event.Data.Object["status"].(string) - - // slack notify(payment log) - field := []string{ - "Type:" + dataType + "(" + event.Type + ")", - "ID:" + event.ID, - "CustomerID:" + customer, - "PlanID:" + planID, - "Start-EndDate:" + fmt.Sprintf(periodStartTime.Format("2006-01-02")+" - "+periodEndTime.Format("2006-01-02")), - "Status:" + status, - "Etc:" + etc, - "Fee:" + strconv.Itoa(int(uint(amount))) + " 円 (" + interval + ")", + groupIDStr := event.Data.Object["metadata"].(map[string]interface{})["group_id"].(string) + etc := "GroupID: " + groupIDStr + ", UserName: " + name + if dataType == "membership" { + groupID, _ = strconv.Atoi(groupIDStr) + + // stripe standard data + customer := event.Data.Object["customer"].(string) + planID := event.Data.Object["plan"].(map[string]interface{})["id"].(string) + amount := event.Data.Object["plan"].(map[string]interface{})["amount"].(float64) + interval := event.Data.Object["plan"].(map[string]interface{})["interval"].(string) + periodStart := event.Data.Object["current_period_start"].(float64) + periodEnd := event.Data.Object["current_period_end"].(float64) + periodStartTime := time.Unix(int64(periodStart), 0) + periodEndTime := time.Unix(int64(periodEnd), 0) + status := event.Data.Object["status"].(string) + jst, _ := time.LoadLocation(config.Conf.Controller.TimeZone) + timeDate := time.Date(periodEndTime.Year(), periodEndTime.Month(), periodEndTime.Day(), 0, 0, 0, 0, jst) + if groupID != 0 { + err = dbGroup.Update(group.UpdateAll, core.Group{Model: gorm.Model{ID: uint(groupID)}, MemberExpired: &timeDate}) + } + + // slack notify(payment log) + field := []string{ + "Type:" + dataType + "(" + event.Type + ")", + "ID:" + event.ID, + "CustomerID:" + customer, + "PlanID:" + planID, + "Start-EndDate:" + fmt.Sprintf(periodStartTime.Format("2006-01-02")+" - "+periodEndTime.Format("2006-01-02")), + "Status:" + status, + "Etc:" + etc, + "Fee:" + strconv.Itoa(int(uint(amount))) + " 円 (" + interval + ")", + } + noticePayment(field) } - noticePayment(field) case "customer.subscription.deleted": // meta dataType := event.Data.Object["metadata"].(map[string]interface{})["type"].(string) diff --git a/pkg/api/core/template/interface.go b/pkg/api/core/template/interface.go index 3a126b2e..d8773ddb 100644 --- a/pkg/api/core/template/interface.go +++ b/pkg/api/core/template/interface.go @@ -17,33 +17,33 @@ type Input struct { } type Result struct { - Services []config.ServiceTemplate `json:"services"` - Connections []config.ConnectionTemplate `json:"connections"` - NTTs []string `json:"ntts"` - IPv4 []string `json:"ipv4"` - IPv6 []string `json:"ipv6"` - IPv4Route []string `json:"ipv4_route"` - IPv6Route []string `json:"ipv6_route"` - PreferredAP []string `json:"preferred_ap"` - PaymentMembershipTemplate []config.MembershipTemplate `json:"payment_membership_template"` + Services []config.ServiceTemplate `json:"services"` + Connections []config.ConnectionTemplate `json:"connections"` + NTTs []string `json:"ntts"` + IPv4 []string `json:"ipv4"` + IPv6 []string `json:"ipv6"` + IPv4Route []string `json:"ipv4_route"` + IPv6Route []string `json:"ipv6_route"` + PreferredAP []string `json:"preferred_ap"` + PaymentMembership []config.MembershipTemplate `json:"payment_membership"` } type ResultAdmin struct { - Services []config.ServiceTemplate `json:"services"` - Connections []config.ConnectionTemplate `json:"connections"` - NTTs []string `json:"ntts"` - NOC []core.NOC `json:"nocs"` - BGPRouter []core.BGPRouter `json:"bgp_router"` - TunnelEndPointRouter []core.TunnelEndPointRouter `json:"tunnel_endpoint_router"` - TunnelEndPointRouterIP []core.TunnelEndPointRouterIP `json:"tunnel_endpoint_router_ip"` - IPv4 []string `json:"ipv4"` - IPv6 []string `json:"ipv6"` - IPv4Route []string `json:"ipv4_route"` - IPv6Route []string `json:"ipv6_route"` - PreferredAP []string `json:"preferred_ap"` - User []core.User `json:"user"` - Group []core.Group `json:"group"` - PaymentMembershipTemplate []config.MembershipTemplate `json:"payment_membership_template"` - MailTemplate []config.MailTemplate `json:"mail_template"` - MemberType []core.ConstantMembership `json:"member_type"` + Services []config.ServiceTemplate `json:"services"` + Connections []config.ConnectionTemplate `json:"connections"` + NTTs []string `json:"ntts"` + NOC []core.NOC `json:"nocs"` + BGPRouter []core.BGPRouter `json:"bgp_router"` + TunnelEndPointRouter []core.TunnelEndPointRouter `json:"tunnel_endpoint_router"` + TunnelEndPointRouterIP []core.TunnelEndPointRouterIP `json:"tunnel_endpoint_router_ip"` + IPv4 []string `json:"ipv4"` + IPv6 []string `json:"ipv6"` + IPv4Route []string `json:"ipv4_route"` + IPv6Route []string `json:"ipv6_route"` + PreferredAP []string `json:"preferred_ap"` + User []core.User `json:"user"` + Group []core.Group `json:"group"` + PaymentMembership []config.MembershipTemplate `json:"payment_membership"` + MailTemplate []config.MailTemplate `json:"mail_template"` + MemberType []core.ConstantMembership `json:"member_type"` } diff --git a/pkg/api/core/template/v0/admin.go b/pkg/api/core/template/v0/admin.go index 9d379ffc..28505880 100644 --- a/pkg/api/core/template/v0/admin.go +++ b/pkg/api/core/template/v0/admin.go @@ -53,21 +53,21 @@ func GetByAdmin(c *gin.Context) { } c.JSON(http.StatusOK, template.ResultAdmin{ - Services: config.Conf.Template.Service, - Connections: config.Conf.Template.Connection, - NTTs: config.Conf.Template.NTT, - NOC: resultNOC.NOC, - BGPRouter: resultBGPRouter.BGPRouter, - TunnelEndPointRouterIP: resultTunnelEndPointRouterIP.TunnelEndPointRouterIP, - IPv4: config.Conf.Template.V4, - IPv6: config.Conf.Template.V6, - IPv4Route: config.Conf.Template.V4Route, - IPv6Route: config.Conf.Template.V6Route, - User: resultUser.User, - Group: resultGroup.Group, - PaymentMembershipTemplate: config.Conf.Template.Membership, - MailTemplate: config.Conf.Template.Mail, - PreferredAP: config.Conf.Template.PreferredAP, + Services: config.Conf.Template.Service, + Connections: config.Conf.Template.Connection, + NTTs: config.Conf.Template.NTT, + NOC: resultNOC.NOC, + BGPRouter: resultBGPRouter.BGPRouter, + TunnelEndPointRouterIP: resultTunnelEndPointRouterIP.TunnelEndPointRouterIP, + IPv4: config.Conf.Template.V4, + IPv6: config.Conf.Template.V6, + IPv4Route: config.Conf.Template.V4Route, + IPv6Route: config.Conf.Template.V6Route, + User: resultUser.User, + Group: resultGroup.Group, + PaymentMembership: config.Conf.Template.Membership, + MailTemplate: config.Conf.Template.Mail, + PreferredAP: config.Conf.Template.PreferredAP, MemberType: []core.ConstantMembership{ core.MemberTypeStandard, core.MemberTypeStudent, diff --git a/pkg/api/core/template/v0/template.go b/pkg/api/core/template/v0/template.go index b4a7660a..6cf0e898 100644 --- a/pkg/api/core/template/v0/template.go +++ b/pkg/api/core/template/v0/template.go @@ -28,14 +28,14 @@ func Get(c *gin.Context) { } c.JSON(http.StatusOK, template.Result{ - Services: resultService, - Connections: config.Conf.Template.Connection, - NTTs: config.Conf.Template.NTT, - IPv4: config.Conf.Template.V4, - IPv6: config.Conf.Template.V6, - IPv4Route: config.Conf.Template.V4Route, - IPv6Route: config.Conf.Template.V6Route, - PreferredAP: config.Conf.Template.PreferredAP, - PaymentMembershipTemplate: config.Conf.Template.Membership, + Services: resultService, + Connections: config.Conf.Template.Connection, + NTTs: config.Conf.Template.NTT, + IPv4: config.Conf.Template.V4, + IPv6: config.Conf.Template.V6, + IPv4Route: config.Conf.Template.V4Route, + IPv6Route: config.Conf.Template.V6Route, + PreferredAP: config.Conf.Template.PreferredAP, + PaymentMembership: config.Conf.Template.Membership, }) } diff --git a/pkg/api/core/tool/config/config.go b/pkg/api/core/tool/config/config.go index 7f482785..87a41627 100644 --- a/pkg/api/core/tool/config/config.go +++ b/pkg/api/core/tool/config/config.go @@ -32,13 +32,14 @@ type Controller struct { type User struct { IP string `json:"ip"` - Url string `json:"url"` Port int `json:"port"` + Url string `json:"url"` } type Admin struct { IP string `json:"ip"` Port int `json:"port"` + Url string `json:"url"` AdminAuth AdminAuth `json:"auth"` } diff --git a/pkg/backend/cmd/start.go b/pkg/backend/cmd/start.go index 3c85c1d8..e675bed2 100644 --- a/pkg/backend/cmd/start.go +++ b/pkg/backend/cmd/start.go @@ -5,6 +5,7 @@ import ( "github.com/homenoc/dsbd-backend/pkg/api/core/tool/config" "github.com/homenoc/dsbd-backend/pkg/api/core/tool/notification" "github.com/spf13/cobra" + "github.com/stripe/stripe-go/v73" "log" ) @@ -31,6 +32,7 @@ var startUserCmd = &cobra.Command{ notification.NoticeLog("good", []string{ "Status: User側 API起動", }) + stripe.Key = config.Conf.Stripe.SecretKey api.UserRestAPI() log.Println("end") @@ -53,6 +55,7 @@ var startAdminCmd = &cobra.Command{ notification.NoticeLog("good", []string{ "Status: Admin側 API起動", }) + stripe.Key = config.Conf.Stripe.SecretKey api.AdminRestAPI() log.Println("end")