修复气站与配送点支付记录数据范围
This commit is contained in:
68
backend/api/internal/logic/common/payment_scope.go
Normal file
68
backend/api/internal/logic/common/payment_scope.go
Normal file
@@ -0,0 +1,68 @@
|
||||
// 功能:统一气站与配送点管理端的支付记录数据范围过滤。
|
||||
// 版本:v1.0。
|
||||
package common
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
const gasPaymentOrderScopeSQL = `
|
||||
(
|
||||
(payment_order.business_type = 'gasorder' AND EXISTS (
|
||||
SELECT 1 FROM gasorder_basic
|
||||
WHERE gasorder_basic.identity = payment_order.business_identity
|
||||
AND gasorder_basic.gas_basic_id = ?
|
||||
))
|
||||
OR (payment_order.business_type = 'ec_order' AND EXISTS (
|
||||
SELECT 1 FROM ec_order
|
||||
WHERE ec_order.identity = payment_order.business_identity
|
||||
AND ec_order.gas_station_id = ?
|
||||
))
|
||||
OR (payment_order.business_type = 'recharge' AND EXISTS (
|
||||
SELECT 1 FROM wallet_recharge_order
|
||||
JOIN wallet_basic ON wallet_basic.id = wallet_recharge_order.wallet_basic_id
|
||||
WHERE wallet_recharge_order.identity = payment_order.business_identity
|
||||
AND wallet_recharge_order.owner_type = 'gas'
|
||||
AND wallet_recharge_order.owner_identity = ?
|
||||
AND wallet_basic.owner_type = 'gas'
|
||||
AND wallet_basic.owner_id = ?
|
||||
AND wallet_basic.owner_identity = ?
|
||||
))
|
||||
)`
|
||||
|
||||
const deliveryPaymentOrderScopeSQL = `
|
||||
(
|
||||
(payment_order.business_type = 'gasorder' AND EXISTS (
|
||||
SELECT 1 FROM gasorder_basic
|
||||
WHERE gasorder_basic.identity = payment_order.business_identity
|
||||
AND gasorder_basic.delivery_basic_id = ?
|
||||
))
|
||||
OR (payment_order.business_type = 'ec_order' AND EXISTS (
|
||||
SELECT 1 FROM ec_order
|
||||
WHERE ec_order.identity = payment_order.business_identity
|
||||
AND ec_order.delivery_point_id = ?
|
||||
))
|
||||
OR (payment_order.business_type = 'recharge' AND EXISTS (
|
||||
SELECT 1 FROM wallet_recharge_order
|
||||
JOIN wallet_basic ON wallet_basic.id = wallet_recharge_order.wallet_basic_id
|
||||
WHERE wallet_recharge_order.identity = payment_order.business_identity
|
||||
AND wallet_recharge_order.owner_type = 'delivery'
|
||||
AND wallet_recharge_order.owner_identity = ?
|
||||
AND wallet_basic.owner_type = 'delivery'
|
||||
AND wallet_basic.owner_id = ?
|
||||
AND wallet_basic.owner_identity = ?
|
||||
))
|
||||
)`
|
||||
|
||||
// ScopePaymentOrdersByOwner 按业务对象验证统一支付单的组织归属。
|
||||
// ownerType 仅接受 gas 或 delivery;ownerID 为组织内部主键,ownerIdentity 为组织业务标识。
|
||||
// 返回值沿用传入查询并追加失败关闭的数据范围条件,未知类型和孤立业务对象不会被放行。
|
||||
func ScopePaymentOrdersByOwner(query *gorm.DB, ownerType string, ownerID uint64, ownerIdentity string) *gorm.DB {
|
||||
switch ownerType {
|
||||
case "gas":
|
||||
return query.Where(gasPaymentOrderScopeSQL, ownerID, ownerID, ownerIdentity, ownerID, ownerIdentity)
|
||||
case "delivery":
|
||||
return query.Where(deliveryPaymentOrderScopeSQL, ownerID, ownerID, ownerIdentity, ownerID, ownerIdentity)
|
||||
default:
|
||||
// 未知组织类型没有可靠归属链路,必须按失败关闭处理。
|
||||
return query.Where("1 = 0")
|
||||
}
|
||||
}
|
||||
107
backend/api/internal/logic/common/payment_scope_test.go
Normal file
107
backend/api/internal/logic/common/payment_scope_test.go
Normal file
@@ -0,0 +1,107 @@
|
||||
// 功能:验证气站与配送点支付记录的数据范围 SQL 和失败关闭行为。
|
||||
// 版本:v1.0。
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// paymentScopeSQL 生成 PostgreSQL 方言下的支付范围查询,供各边界用例断言。
|
||||
func paymentScopeSQL(t *testing.T, ownerType string, ownerID uint64, ownerIdentity string, paymentIdentity string) string {
|
||||
t.Helper()
|
||||
connection, _, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = connection.Close() })
|
||||
database, err := gorm.Open(postgres.New(postgres.Config{Conn: connection}), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return database.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||
query := ScopePaymentOrdersByOwner(ActiveRecords(tx.Model(&models.PaymentOrder{})), ownerType, ownerID, ownerIdentity)
|
||||
if paymentIdentity != "" {
|
||||
query = query.Where("payment_order.identity = ?", paymentIdentity)
|
||||
}
|
||||
return query.Find(&[]models.PaymentOrder{})
|
||||
})
|
||||
}
|
||||
|
||||
// assertSQLContains 校验查询必须包含所有数据范围片段。
|
||||
func assertSQLContains(t *testing.T, statement string, fragments ...string) {
|
||||
t.Helper()
|
||||
for _, fragment := range fragments {
|
||||
if !strings.Contains(statement, fragment) {
|
||||
t.Fatalf("支付范围查询缺少 %q:%s", fragment, statement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestScopePaymentOrdersByGas 验证三类支付只能通过当前气站业务对象进入结果集。
|
||||
func TestScopePaymentOrdersByGas(t *testing.T) {
|
||||
statement := paymentScopeSQL(t, "gas", 11, "gas-identity", "")
|
||||
assertSQLContains(t, statement,
|
||||
`payment_order.business_type = 'gasorder'`,
|
||||
`gasorder_basic.gas_basic_id = 11`,
|
||||
`payment_order.business_type = 'ec_order'`,
|
||||
`ec_order.gas_station_id = 11`,
|
||||
`payment_order.business_type = 'recharge'`,
|
||||
`wallet_recharge_order.owner_type = 'gas'`,
|
||||
`wallet_recharge_order.owner_identity = 'gas-identity'`,
|
||||
`wallet_basic.owner_type = 'gas'`,
|
||||
`wallet_basic.owner_id = 11`,
|
||||
`wallet_basic.owner_identity = 'gas-identity'`,
|
||||
)
|
||||
if strings.Contains(statement, "payment_order.wallet_basic_id") {
|
||||
t.Fatalf("统一支付单仍错误依赖钱包主键:%s", statement)
|
||||
}
|
||||
if strings.Contains(statement, "delivery_basic_id") || strings.Contains(statement, "delivery_point_id") {
|
||||
t.Fatalf("气站范围查询混入配送点归属字段:%s", statement)
|
||||
}
|
||||
}
|
||||
|
||||
// TestScopePaymentOrdersByDelivery 验证三类支付只能通过当前配送点业务对象进入结果集。
|
||||
func TestScopePaymentOrdersByDelivery(t *testing.T) {
|
||||
statement := paymentScopeSQL(t, "delivery", 22, "delivery-identity", "")
|
||||
assertSQLContains(t, statement,
|
||||
`payment_order.business_type = 'gasorder'`,
|
||||
`gasorder_basic.delivery_basic_id = 22`,
|
||||
`payment_order.business_type = 'ec_order'`,
|
||||
`ec_order.delivery_point_id = 22`,
|
||||
`payment_order.business_type = 'recharge'`,
|
||||
`wallet_recharge_order.owner_type = 'delivery'`,
|
||||
`wallet_recharge_order.owner_identity = 'delivery-identity'`,
|
||||
`wallet_basic.owner_type = 'delivery'`,
|
||||
`wallet_basic.owner_id = 22`,
|
||||
`wallet_basic.owner_identity = 'delivery-identity'`,
|
||||
)
|
||||
if strings.Contains(statement, "payment_order.wallet_basic_id") {
|
||||
t.Fatalf("统一支付单仍错误依赖钱包主键:%s", statement)
|
||||
}
|
||||
if strings.Contains(statement, "gasorder_basic.gas_basic_id") || strings.Contains(statement, "ec_order.gas_station_id") {
|
||||
t.Fatalf("配送点范围查询混入气站归属字段:%s", statement)
|
||||
}
|
||||
}
|
||||
|
||||
// TestScopePaymentOrdersByOwnerFailsClosed 验证未知组织类型不会放行任何支付记录。
|
||||
func TestScopePaymentOrdersByOwnerFailsClosed(t *testing.T) {
|
||||
statement := paymentScopeSQL(t, "unknown", 33, "unknown-identity", "")
|
||||
assertSQLContains(t, statement, "1 = 0")
|
||||
}
|
||||
|
||||
// TestScopePaymentOrderDetailKeepsOwnerBoundary 验证详情定位不会绕过与列表相同的组织范围。
|
||||
func TestScopePaymentOrderDetailKeepsOwnerBoundary(t *testing.T) {
|
||||
statement := paymentScopeSQL(t, "delivery", 44, "delivery-detail", "payment-detail")
|
||||
assertSQLContains(t, statement,
|
||||
`gasorder_basic.delivery_basic_id = 44`,
|
||||
`ec_order.delivery_point_id = 44`,
|
||||
`wallet_basic.owner_identity = 'delivery-detail'`,
|
||||
`payment_order.identity = 'payment-detail'`,
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// 功能:提供配送点管理端钱包、支付、充值、提现及结算接口。
|
||||
// 版本:v1.1。
|
||||
package delivery
|
||||
|
||||
import (
|
||||
@@ -55,10 +57,24 @@ func listWalletChild(ctx *gin.Context, model any, table string) {
|
||||
listScoped(ctx, model, query, table+".created_at desc")
|
||||
}
|
||||
|
||||
func ListBank(ctx *gin.Context) { listWalletChild(ctx, &models.WalletBank{}, "wallet_bank") }
|
||||
func ListPayment(ctx *gin.Context) { listWalletChild(ctx, &models.PaymentOrder{}, "payment_order") }
|
||||
func ListRecord(ctx *gin.Context) { listWalletChild(ctx, &models.WalletRecord{}, "wallet_record") }
|
||||
func ListRefund(ctx *gin.Context) { listWalletChild(ctx, &models.PaymentRefund{}, "payment_refund") }
|
||||
func ListBank(ctx *gin.Context) { listWalletChild(ctx, &models.WalletBank{}, "wallet_bank") }
|
||||
func ListRecord(ctx *gin.Context) { listWalletChild(ctx, &models.WalletRecord{}, "wallet_record") }
|
||||
func ListRefund(ctx *gin.Context) { listWalletChild(ctx, &models.PaymentRefund{}, "payment_refund") }
|
||||
|
||||
// paymentOrderQuery 按统一支付单关联的业务对象限定当前配送点数据范围。
|
||||
func paymentOrderQuery(point models.DeliveryBasic) *gorm.DB {
|
||||
query := common.ActiveRecords(db().Model(&models.PaymentOrder{}))
|
||||
return common.ScopePaymentOrdersByOwner(query, "delivery", point.ID, point.Identity)
|
||||
}
|
||||
|
||||
// ListPayment 返回当前配送点业务范围内的统一支付记录。
|
||||
func ListPayment(ctx *gin.Context) {
|
||||
point, _, ok := currentScope(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
listScoped(ctx, &models.PaymentOrder{}, paymentOrderQuery(point), "payment_order.created_at desc")
|
||||
}
|
||||
func ListRecharge(ctx *gin.Context) {
|
||||
point, _, ok := currentScope(ctx)
|
||||
if !ok {
|
||||
@@ -86,10 +102,19 @@ func getWalletChild(ctx *gin.Context, model any, table string) {
|
||||
respondRecord(ctx, query, model)
|
||||
}
|
||||
|
||||
func GetBank(ctx *gin.Context) { getWalletChild(ctx, &models.WalletBank{}, "wallet_bank") }
|
||||
func GetPayment(ctx *gin.Context) { getWalletChild(ctx, &models.PaymentOrder{}, "payment_order") }
|
||||
func GetRecord(ctx *gin.Context) { getWalletChild(ctx, &models.WalletRecord{}, "wallet_record") }
|
||||
func GetRefund(ctx *gin.Context) { getWalletChild(ctx, &models.PaymentRefund{}, "payment_refund") }
|
||||
func GetBank(ctx *gin.Context) { getWalletChild(ctx, &models.WalletBank{}, "wallet_bank") }
|
||||
func GetRecord(ctx *gin.Context) { getWalletChild(ctx, &models.WalletRecord{}, "wallet_record") }
|
||||
func GetRefund(ctx *gin.Context) { getWalletChild(ctx, &models.PaymentRefund{}, "payment_refund") }
|
||||
|
||||
// GetPayment 返回当前配送点业务范围内的单条统一支付记录。
|
||||
func GetPayment(ctx *gin.Context) {
|
||||
point, _, ok := currentScope(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
query := paymentOrderQuery(point).Where("payment_order.identity = ?", ctx.Param("identity"))
|
||||
respondRecord(ctx, query, &models.PaymentOrder{})
|
||||
}
|
||||
func GetRecharge(ctx *gin.Context) {
|
||||
point, _, ok := currentScope(ctx)
|
||||
if !ok {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// 功能:提供气站管理端钱包、支付、退款、提现、结算及对账接口。
|
||||
// 版本:v1.1。
|
||||
package gas
|
||||
|
||||
import (
|
||||
@@ -70,11 +72,30 @@ func getWalletChild(ctx *gin.Context, model any, table string) {
|
||||
|
||||
func ListWalletBank(ctx *gin.Context) { listWalletChild(ctx, &models.WalletBank{}, "wallet_bank") }
|
||||
func GetWalletBank(ctx *gin.Context) { getWalletChild(ctx, &models.WalletBank{}, "wallet_bank") }
|
||||
func ListPaymentOrder(ctx *gin.Context) {
|
||||
listWalletChild(ctx, &models.PaymentOrder{}, "payment_order")
|
||||
|
||||
// paymentOrderQuery 按统一支付单关联的业务对象限定当前气站数据范围。
|
||||
func paymentOrderQuery(station models.GasBasic) *gorm.DB {
|
||||
query := common.ActiveRecords(impl.DBService.Model(&models.PaymentOrder{}))
|
||||
return common.ScopePaymentOrdersByOwner(query, "gas", station.ID, station.Identity)
|
||||
}
|
||||
|
||||
// ListPaymentOrder 返回当前气站业务范围内的统一支付记录。
|
||||
func ListPaymentOrder(ctx *gin.Context) {
|
||||
station, ok := currentGas(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
listScoped(ctx, &models.PaymentOrder{}, paymentOrderQuery(station), "payment_order.created_at desc")
|
||||
}
|
||||
|
||||
// GetPaymentOrder 返回当前气站业务范围内的单条统一支付记录。
|
||||
func GetPaymentOrder(ctx *gin.Context) {
|
||||
getWalletChild(ctx, &models.PaymentOrder{}, "payment_order")
|
||||
station, ok := currentGas(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
query := paymentOrderQuery(station).Where("payment_order.identity = ?", ctx.Param("identity"))
|
||||
respondScopedRecord(ctx, query, &models.PaymentOrder{})
|
||||
}
|
||||
func ListWalletRecord(ctx *gin.Context) {
|
||||
listWalletChild(ctx, &models.WalletRecord{}, "wallet_record")
|
||||
|
||||
Reference in New Issue
Block a user