feat: add charge
This commit is contained in:
Binary file not shown.
@@ -11,7 +11,8 @@ func (f *Middlewares) ProcessResponse(c fiber.Ctx) error {
|
|||||||
log.WithField("module", "middleware.ProcessResponse").Debug("Begin")
|
log.WithField("module", "middleware.ProcessResponse").Debug("Begin")
|
||||||
defer log.WithField("module", "middleware.ProcessResponse").Debug("END")
|
defer log.WithField("module", "middleware.ProcessResponse").Debug("END")
|
||||||
|
|
||||||
if err := c.Next(); err != nil {
|
err := c.Next()
|
||||||
|
if err != nil {
|
||||||
log.WithError(err).Error("process response error")
|
log.WithError(err).Error("process response error")
|
||||||
|
|
||||||
if e, ok := err.(errorx.Response); ok {
|
if e, ok := err.(errorx.Response); ok {
|
||||||
@@ -29,5 +30,5 @@ func (f *Middlewares) ProcessResponse(c fiber.Ctx) error {
|
|||||||
return errorx.Wrap(err).Response(c)
|
return errorx.Wrap(err).Response(c)
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,3 +33,19 @@ func (c *Controller) Charge(ctx fiber.Ctx) error {
|
|||||||
|
|
||||||
return ctx.JSON(nil)
|
return ctx.JSON(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Info
|
||||||
|
func (c *Controller) Info(ctx fiber.Ctx) error {
|
||||||
|
claim := fiber.Locals[*jwt.Claims](ctx, consts.CtxKeyClaim)
|
||||||
|
log.Debug(claim)
|
||||||
|
|
||||||
|
info := &UserInfo{}
|
||||||
|
|
||||||
|
balance, err := c.svc.GetTenantUserBalance(ctx.Context(), claim.TenantID, claim.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info.Balance = balance
|
||||||
|
|
||||||
|
return ctx.JSON(info)
|
||||||
|
}
|
||||||
|
|||||||
5
backend/modules/users/dto.go
Normal file
5
backend/modules/users/dto.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
type UserInfo struct {
|
||||||
|
Balance int64 `json:"balance"`
|
||||||
|
}
|
||||||
@@ -26,6 +26,6 @@ func (r *Router) Name() string {
|
|||||||
|
|
||||||
func (r *Router) Register(router fiber.Router) {
|
func (r *Router) Register(router fiber.Router) {
|
||||||
group := router.Group(r.Name())
|
group := router.Group(r.Name())
|
||||||
group.Get("", r.controller.List)
|
group.Get("info", r.controller.Info)
|
||||||
group.Patch("charge/:code", r.controller.Charge)
|
group.Patch("charge/:code", r.controller.Charge)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,13 +174,13 @@ func (svc *Service) TenantHasUser(ctx context.Context, userID, tenantID int64) (
|
|||||||
log.Debug(stmt.DebugSql())
|
log.Debug(stmt.DebugSql())
|
||||||
|
|
||||||
var result struct {
|
var result struct {
|
||||||
cnt int64
|
Cnt int64
|
||||||
}
|
}
|
||||||
if err := stmt.QueryContext(ctx, db.FromContext(ctx, svc.db), &result); err != nil {
|
if err := stmt.QueryContext(ctx, db.FromContext(ctx, svc.db), &result); err != nil {
|
||||||
return false, errors.Wrap(err, "failed to query user-tenant relation")
|
return false, errors.Wrap(err, "failed to query user-tenant relation")
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.cnt > 0, nil
|
return result.Cnt > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTenantUser
|
// CreateTenantUser
|
||||||
@@ -268,7 +268,7 @@ func (svc *Service) GenerateChargeCode(ctx context.Context, tenantID, chargeAmou
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "failed to encode charge code")
|
return "", errors.Wrap(err, "failed to encode charge code")
|
||||||
}
|
}
|
||||||
log.Infof("generate charge code: %s", code)
|
log.Infof("generate charge code: %s, info: %+v", code, []int64{tenantID, chargeAmount, timestamp})
|
||||||
|
|
||||||
return code, nil
|
return code, nil
|
||||||
}
|
}
|
||||||
@@ -379,3 +379,20 @@ func (svc *Service) Charge(ctx context.Context, claim *jwt.Claims, code string)
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTenantUserBalance
|
||||||
|
func (svc *Service) GetTenantUserBalance(ctx context.Context, tenantID, userID int64) (int64, error) {
|
||||||
|
log := svc.log.WithField("method", "GetTenantUserBalance")
|
||||||
|
|
||||||
|
tbl := table.UsersTenants
|
||||||
|
stmt := tbl.SELECT(tbl.Balance.AS("balance")).WHERE(tbl.TenantID.EQ(Int64(tenantID)).AND(tbl.UserID.EQ(Int64(userID))))
|
||||||
|
log.Debug(stmt.DebugSql())
|
||||||
|
|
||||||
|
var result struct {
|
||||||
|
Balance int64
|
||||||
|
}
|
||||||
|
if err := stmt.QueryContext(ctx, db.FromContext(ctx, svc.db), &result); err != nil {
|
||||||
|
return 0, errors.Wrap(err, "failed to query user balance")
|
||||||
|
}
|
||||||
|
return result.Balance, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func Test_DiscoverMedias(t *testing.T) {
|
|||||||
|
|
||||||
func (t *ServiceTestSuite) Test_Charge() {
|
func (t *ServiceTestSuite) Test_Charge() {
|
||||||
Convey("Charge", t.T(), func() {
|
Convey("Charge", t.T(), func() {
|
||||||
code, err := t.Svc.GenerateChargeCode(context.Background(), 1, 100)
|
code, err := t.Svc.GenerateChargeCode(context.Background(), 2, 100)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
code = "b8TDWf59wvPw"
|
code = "b8TDWf59wvPw"
|
||||||
|
|
||||||
@@ -57,3 +57,12 @@ func (t *ServiceTestSuite) Test_Charge() {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test_GetTenantUserBalance
|
||||||
|
func (t *ServiceTestSuite) Test_GetTenantUserBalance() {
|
||||||
|
Convey("GetTenantUserBalance", t.T(), func() {
|
||||||
|
balance, err := t.Svc.GetTenantUserBalance(context.Background(), 1, 1)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(balance, ShouldNotEqual, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func (r Response) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r Response) Response(ctx fiber.Ctx) error {
|
func (r Response) Response(ctx fiber.Ctx) error {
|
||||||
return ctx.Status(r.Code).JSON(r)
|
return ctx.Status(r.StatusCode).JSON(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const router = createRouter({
|
|||||||
// route level code-splitting
|
// route level code-splitting
|
||||||
// this generates a separate chunk (About.[hash].js) for this route
|
// this generates a separate chunk (About.[hash].js) for this route
|
||||||
// which is lazy-loaded when the route is visited.
|
// which is lazy-loaded when the route is visited.
|
||||||
component: () => import('../views/tabs/MeView.vue'),
|
component: () => import('../views/tabs/UserView.vue'),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="about">
|
|
||||||
<h1>This is an about page</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
86
frontend/src/views/tabs/UserView.vue
Normal file
86
frontend/src/views/tabs/UserView.vue
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<van-image width="100%" height="100" src="https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg" />
|
||||||
|
|
||||||
|
<!-- 可以使用 CellGroup 作为容器 -->
|
||||||
|
<van-cell-group>
|
||||||
|
<van-cell size="large" title="账户余额" :value="user.balance ?? '--'" />
|
||||||
|
<van-field v-model="chargeCode" center clearable label="充值" placeholder="请输入充值码" size="large">
|
||||||
|
<template #button>
|
||||||
|
<van-button size="small" type="primary" @click="confirmCharge">确认充值</van-button>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
const user = ref({});
|
||||||
|
const chargeCode = ref("")
|
||||||
|
|
||||||
|
const loadUserInfo = () => {
|
||||||
|
request.get("/users/info").then((res) => {
|
||||||
|
user.value = res.data;
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadUserInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
const confirmCharge = () => {
|
||||||
|
if (!chargeCode.value) {
|
||||||
|
return showFailToast('请输入充值码');
|
||||||
|
}
|
||||||
|
|
||||||
|
showConfirmDialog({
|
||||||
|
title: '充值确认',
|
||||||
|
message: "充值成功后此充值码将无法再次使用!",
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
request
|
||||||
|
.patch(`/users/charge/${chargeCode.value}`)
|
||||||
|
.then(() => {
|
||||||
|
loadUserInfo();
|
||||||
|
showSuccessToast('充值成功');
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
let message = err.response.data.message;
|
||||||
|
showFailToast(message);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("取消充值");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.balance {
|
||||||
|
padding: 2em 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: azure;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance .title {
|
||||||
|
font-size: 1.5em;
|
||||||
|
color: #666;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance .num {
|
||||||
|
font-size: 2em;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user