Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(account-service): add use giftcode #5013

Merged
merged 4 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions controllers/pkg/database/cockroach/accountv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -1054,3 +1054,48 @@ func (c *Cockroach) Close() error {
}
return db.Close()
}

func (c *Cockroach) GetGiftCodeWithCode(code string) (*types.GiftCode, error) {
var giftCode types.GiftCode
if err := c.DB.Where(&types.GiftCode{Code: code}).First(&giftCode).Error; err != nil {
return nil, fmt.Errorf("failed to get gift code: %w", err)
}
return &giftCode, nil
}

func (c *Cockroach) UseGiftCode(giftCode *types.GiftCode, userID string) error {
return c.DB.Transaction(func(tx *gorm.DB) error {
ops := &types.UserQueryOpts{ID: userID}
// Update the user's balance
if err := c.updateBalance(tx, ops, giftCode.CreditAmount, false, true); err != nil {
return fmt.Errorf("failed to update user balance: %w", err)
}

message := "created by use gift code"
// Create an AccountTransaction record
accountTransaction := &types.AccountTransaction{
ID: uuid.New(),
Type: "GiftCode",
UserUID: ops.UID,
DeductionBalance: 0,
Balance: giftCode.CreditAmount,
Message: &message,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
BillingID: giftCode.ID,
}
if err := tx.Create(accountTransaction).Error; err != nil {
return fmt.Errorf("failed to create account transaction: %w", err)
}

// Mark the gift code as used
giftCode.Used = true
giftCode.UsedBy = ops.UID
giftCode.UsedAt = time.Now()
if err := tx.Save(giftCode).Error; err != nil {
return fmt.Errorf("failed to update gift code: %w", err)
}

return nil
})
}
32 changes: 32 additions & 0 deletions controllers/pkg/types/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,35 @@ func (Invoice) TableName() string {
func (InvoicePayment) TableName() string {
return "InvoicePayment"
}

type GiftCode struct {
ID uuid.UUID `gorm:"column:id;type:uuid;default:gen_random_uuid();primary_key"`
Code string `gorm:"column:code;type:text;not null;unique"`
CreditAmount int64 `gorm:"column:creditAmount;type:bigint;default:0;not null"`
Used bool `gorm:"column:used;type:boolean;default:false;not null"`
UsedBy uuid.UUID `gorm:"column:usedBy;type:uuid"`
UsedAt time.Time `gorm:"column:usedAt;type:timestamp(3) with time zone"`
CreatedAt time.Time `gorm:"column:createdAt;type:timestamp(3) with time zone;default:current_timestamp()"`
ExpiredAt time.Time `gorm:"column:expiredAt;type:timestamp(3) with time zone"`
Comment string `gorm:"column:comment;type:text"`
}

func (GiftCode) TableName() string {
return "GiftCode"
}

type AccountTransaction struct {
ID uuid.UUID `gorm:"column:id;type:uuid;default:gen_random_uuid();primary_key"`
Type string `gorm:"column:type;type:text"`
UserUID uuid.UUID `gorm:"column:userUid;type:uuid"`
DeductionBalance int64 `gorm:"column:deduction_balance;type:bigint"`
Balance int64 `gorm:"column:balance;type:bigint"`
Message *string `gorm:"column:message;type:text"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamp(3) with time zone;default:current_timestamp()"`
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp(3) with time zone;default:current_timestamp()"`
BillingID uuid.UUID `gorm:"column:billing_id;type:uuid"`
}

func (AccountTransaction) TableName() string {
return "AccountTransaction"
}
41 changes: 41 additions & 0 deletions service/account/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -875,3 +875,44 @@ func GetInvoicePayment(c *gin.Context) {
"data": payments,
})
}

// UseGiftCode
// @Summary Use a gift code
// @Description Redeem a gift code and apply the credit to the user's account
// @Tags UseGiftCode
// @Accept json
// @Produce json
// @Param request body helper.UseGiftCodeReq true "Use gift code request"
// @Success 200 {object} helper.UseGiftCodeResp "Successfully redeemed gift code"
// @Failure 400 {object} helper.ErrorMessage "Failed to parse use gift code request"
// @Failure 401 {object} helper.ErrorMessage "Authentication error"
// @Failure 500 {object} helper.ErrorMessage "Failed to redeem gift code"
// @Router /account/v1alpha1/gift-code/use [post]
func UseGiftCode(c *gin.Context) {
// Parse the use gift code request
req, err := helper.ParseUseGiftCodeReq(c)
if err != nil {
c.JSON(http.StatusBadRequest, helper.ErrorMessage{Error: fmt.Sprintf("failed to parse use gift code request: %v", err)})
return
}

// Check authentication
if err := CheckAuthAndCalibrate(req.Auth); err != nil {
c.JSON(http.StatusUnauthorized, helper.ErrorMessage{Error: fmt.Sprintf("authenticate error: %v", err)})
return
}

// Use the gift code (to be implemented)
if _, err := dao.DBClient.UseGiftCode(req); err != nil {
c.JSON(http.StatusInternalServerError, helper.ErrorMessage{Error: fmt.Sprintf("failed to use gift code: %v", err)})
return
}

// Return success response
c.JSON(http.StatusOK, helper.UseGiftCodeResp{
Data: helper.UseGiftCodeRespData{
UserID: req.UserID,
},
Message: "Gift code successfully redeemed",
})
}
22 changes: 22 additions & 0 deletions service/account/dao/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type Interface interface {
GetUserCrName(ops types.UserQueryOpts) (string, error)
GetRegions() ([]types.Region, error)
GetLocalRegion() types.Region
UseGiftCode(req *helper.UseGiftCodeReq) (*types.GiftCode, error)
}

type Account struct {
Expand Down Expand Up @@ -1358,3 +1359,24 @@ func (m *Account) GetInvoicePayments(invoiceID string) ([]types.Payment, error)
func (m *Account) SetStatusInvoice(req *helper.SetInvoiceStatusReq) error {
return m.ck.SetInvoiceStatus(req.InvoiceIDList, req.Status)
}

func (m *Account) UseGiftCode(req *helper.UseGiftCodeReq) (*types.GiftCode, error) {
giftCode, err := m.ck.GetGiftCodeWithCode(req.Code)
if err != nil {
return nil, fmt.Errorf("failed to get gift code: %v", err)
}

if !giftCode.ExpiredAt.IsZero() && time.Now().After(giftCode.ExpiredAt) {
return nil, fmt.Errorf("gift code has expired")
}

if giftCode.Used {
return nil, fmt.Errorf("gift code is already used")
}

if err = m.ck.UseGiftCode(giftCode, req.UserID); err != nil {
return nil, fmt.Errorf("failed to use gift code: %v", err)
}

return giftCode, nil
}
21 changes: 21 additions & 0 deletions service/account/dao/interface_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,27 @@ func TestAccount_SetStatusInvoice(t *testing.T) {
}
}

func TestAccount_UseGiftCode(t *testing.T) {
db, err := newAccountForTest("", os.Getenv("GLOBAL_COCKROACH_URI"), os.Getenv("LOCAL_COCKROACH_URI"))
if err != nil {
t.Fatalf("NewAccountInterface() error = %v", err)
return
}

giftcode, err := db.UseGiftCode(&helper.UseGiftCodeReq{
Code: "DfxAffaeEf",
Auth: &helper.Auth{
UserID: "E1xAJ0fy4k",
},
})

if err != nil {
t.Fatalf("UseGiftCode() error = %v", err)
return
}
t.Logf("giftcode = %+v", giftcode)
}

func init() {
// set env
os.Setenv("MONGO_URI", "")
Expand Down
101 changes: 101 additions & 0 deletions service/account/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,58 @@ const docTemplate = `{
}
}
},
"/account/v1alpha1/gift-code/use": {
"post": {
"description": "Redeem a gift code and apply the credit to the user's account",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"UseGiftCode"
],
"summary": "Use a gift code",
"parameters": [
{
"description": "Use gift code request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/helper.UseGiftCodeReq"
}
}
],
"responses": {
"200": {
"description": "Successfully redeemed gift code",
"schema": {
"$ref": "#/definitions/helper.UseGiftCodeResp"
}
},
"400": {
"description": "Failed to parse use gift code request",
"schema": {
"$ref": "#/definitions/helper.ErrorMessage"
}
},
"401": {
"description": "Authentication error",
"schema": {
"$ref": "#/definitions/helper.ErrorMessage"
}
},
"500": {
"description": "Failed to redeem gift code",
"schema": {
"$ref": "#/definitions/helper.ErrorMessage"
}
}
}
}
},
"/account/v1alpha1/invoice/apply": {
"post": {
"description": "Apply invoice",
Expand Down Expand Up @@ -1858,6 +1910,55 @@ const docTemplate = `{
}
}
},
"helper.UseGiftCodeReq": {
"type": "object",
"required": [
"code"
],
"properties": {
"code": {
"description": "@Summary Gift code to be used\n@Description The code of the gift card to be redeemed\n@JSONSchema required",
"type": "string",
"example": "HAPPY2024"
},
"kubeConfig": {
"type": "string"
},
"owner": {
"type": "string",
"example": "admin"
},
"token": {
"type": "string",
"example": "token"
},
"userID": {
"type": "string",
"example": "admin"
}
}
},
"helper.UseGiftCodeResp": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/helper.UseGiftCodeRespData"
},
"message": {
"type": "string",
"example": "Gift code successfully redeemed"
}
}
},
"helper.UseGiftCodeRespData": {
"type": "object",
"properties": {
"userID": {
"type": "string",
"example": "user-123"
}
}
},
"helper.UserBaseReq": {
"type": "object",
"properties": {
Expand Down
Loading
Loading