优化结构
This commit is contained in:
@@ -3,7 +3,7 @@ package data
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"kra/internal/biz/system"
|
||||
integrationbiz "kra/internal/biz/integration"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -13,15 +13,15 @@ func ensureCommunicationIntegrationConfigs(db *gorm.DB) error {
|
||||
kind string
|
||||
provider string
|
||||
}{
|
||||
{kind: system.IntegrationKindMQ, provider: "emqx"},
|
||||
{kind: system.IntegrationKindMQ, provider: "rabbitmq"},
|
||||
{kind: system.IntegrationKindWebSocket, provider: "melody"},
|
||||
{kind: integrationbiz.IntegrationKindMQ, provider: "emqx"},
|
||||
{kind: integrationbiz.IntegrationKindMQ, provider: "rabbitmq"},
|
||||
{kind: integrationbiz.IntegrationKindWebSocket, provider: "melody"},
|
||||
}
|
||||
for _, item := range defaults {
|
||||
var row integrationConfigPO
|
||||
err := db.Where("kind = ? AND provider = ?", item.kind, item.provider).First(&row).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
values, marshalErr := json.Marshal(system.DefaultIntegrationConfig(item.kind, item.provider))
|
||||
values, marshalErr := json.Marshal(integrationbiz.DefaultIntegrationConfig(item.kind, item.provider))
|
||||
if marshalErr != nil {
|
||||
return marshalErr
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"kra/internal/biz/system"
|
||||
integrationbiz "kra/internal/biz/integration"
|
||||
bizpayment "kra/internal/biz/payment"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -21,13 +22,13 @@ import (
|
||||
|
||||
type paymentRepo struct{ data Provider }
|
||||
|
||||
func NewPaymentRepo(data Provider) system.PaymentRepo { return &paymentRepo{data: data} }
|
||||
func NewPaymentRepo(data Provider) bizpayment.PaymentRepo { return &paymentRepo{data: data} }
|
||||
|
||||
func ensurePaymentIntegrationConfigs(db *gorm.DB) error {
|
||||
for _, provider := range system.SupportedPaymentProviders {
|
||||
for _, provider := range bizpayment.SupportedPaymentProviders {
|
||||
var row integrationConfigPO
|
||||
err := db.Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error
|
||||
defaults := system.DefaultIntegrationConfig(integrationKindPayment, provider)
|
||||
defaults := integrationbiz.DefaultIntegrationConfig(integrationKindPayment, provider)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
encoded, _ := json.Marshal(defaults)
|
||||
if err := db.Create(&integrationConfigPO{Kind: integrationKindPayment, Provider: provider, Enabled: false, Config: string(encoded)}).Error; err != nil {
|
||||
@@ -61,7 +62,7 @@ func (r *paymentRepo) row(ctx context.Context, provider string) (*integrationCon
|
||||
var row integrationConfigPO
|
||||
if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil, system.ErrPaymentProviderNotFound
|
||||
return nil, nil, bizpayment.ErrPaymentProviderNotFound
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -84,11 +85,11 @@ func (r *paymentRepo) adapter(ctx context.Context, provider string) (datapayment
|
||||
return adapter, values, err
|
||||
}
|
||||
|
||||
func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*system.PaymentTestResult, error) {
|
||||
func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*bizpayment.PaymentTestResult, error) {
|
||||
started := time.Now()
|
||||
test := &system.PaymentTestResult{Provider: provider, TradeNo: "", Passed: false, Stages: []system.PaymentTestStage{}}
|
||||
test := &bizpayment.PaymentTestResult{Provider: provider, TradeNo: "", Passed: false, Stages: []bizpayment.PaymentTestStage{}}
|
||||
add := func(name, status, message, tradeNo string, since time.Time) {
|
||||
test.Stages = append(test.Stages, system.PaymentTestStage{Name: name, Status: status, Message: message, TradeNo: tradeNo, Duration: time.Since(since).Milliseconds()})
|
||||
test.Stages = append(test.Stages, bizpayment.PaymentTestStage{Name: name, Status: status, Message: message, TradeNo: tradeNo, Duration: time.Since(since).Milliseconds()})
|
||||
}
|
||||
values, err := r.testRow(ctx, provider)
|
||||
if err != nil {
|
||||
@@ -97,7 +98,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste
|
||||
}
|
||||
test.Mode = strings.ToLower(strings.TrimSpace(text(values, "environment")))
|
||||
configStart := time.Now()
|
||||
if err = system.ValidateIntegrationConfig(system.IntegrationKindPayment, provider, values); err != nil {
|
||||
if err = integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, provider, values); err != nil {
|
||||
add("config", "failed", err.Error(), "", configStart)
|
||||
return test, err
|
||||
}
|
||||
@@ -117,11 +118,11 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste
|
||||
orders := &paymentOrderRepo{data: r.data}
|
||||
extra, _ := json.Marshal(req.Extra)
|
||||
localStart := time.Now()
|
||||
order, _, err := orders.CreatePaymentOrder(ctx, &system.PaymentOrder{
|
||||
order, _, err := orders.CreatePaymentOrder(ctx, &bizpayment.PaymentOrder{
|
||||
TradeNo: req.TradeNo, Provider: provider, BusinessType: req.BusinessType, BusinessID: req.BusinessID,
|
||||
Subject: req.Subject, PaymentMode: system.PaymentModeExternal, OriginalAmount: req.Amount, Amount: req.Amount,
|
||||
Currency: req.Currency, PaymentStatus: system.PaymentStatusInitialized, FulfillmentStatus: system.FulfillmentStatusPending,
|
||||
RefundStatus: system.RefundStatusNone, ConfirmationID: uuid.NewString(), RequestFingerprint: paymentTestFingerprint(req), Extra: extra,
|
||||
Subject: req.Subject, PaymentMode: bizpayment.PaymentModeExternal, OriginalAmount: req.Amount, Amount: req.Amount,
|
||||
Currency: req.Currency, PaymentStatus: bizpayment.PaymentStatusInitialized, FulfillmentStatus: bizpayment.FulfillmentStatusPending,
|
||||
RefundStatus: bizpayment.RefundStatusNone, ConfirmationID: uuid.NewString(), RequestFingerprint: paymentTestFingerprint(req), Extra: extra,
|
||||
})
|
||||
if err != nil {
|
||||
add("local_order", "failed", err.Error(), req.TradeNo, localStart)
|
||||
@@ -159,7 +160,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste
|
||||
if queryID == "" {
|
||||
queryID = req.TradeNo
|
||||
}
|
||||
if provider == system.PaymentApple {
|
||||
if provider == bizpayment.PaymentApple {
|
||||
queryID = strings.TrimSpace(text(values, "test_transaction_id"))
|
||||
if queryID == "" {
|
||||
err = errors.New("Apple 连通性测试需要配置 test_transaction_id(沙箱交易 ID)")
|
||||
@@ -179,7 +180,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste
|
||||
add("query", "failed", err.Error(), req.TradeNo, queryStart)
|
||||
return test, err
|
||||
}
|
||||
if provider != system.PaymentApple {
|
||||
if provider != bizpayment.PaymentApple {
|
||||
if order, err = orders.ApplyPaymentResult(ctx, provider, req.TradeNo, paymentTestProviderUpdate(queried)); err != nil {
|
||||
add("local_order", "failed", "回写测试查单结果失败: "+err.Error(), req.TradeNo, queryStart)
|
||||
return test, err
|
||||
@@ -193,9 +194,9 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste
|
||||
test.Result = queried
|
||||
add("query", "passed", "测试订单查询成功,状态: "+queried.Status, req.TradeNo, queryStart)
|
||||
|
||||
if queried.Status != "success" || provider == system.PaymentApple {
|
||||
if queried.Status != "success" || provider == bizpayment.PaymentApple {
|
||||
message := "订单尚未支付成功,已完成配置、下单和查单连通性测试;请在沙箱完成付款后重试"
|
||||
if provider == system.PaymentApple {
|
||||
if provider == bizpayment.PaymentApple {
|
||||
message = "Apple 退款由 App Store 管理,已完成配置、下单和交易查询测试"
|
||||
}
|
||||
add("refund", "skipped", message, req.TradeNo, time.Now())
|
||||
@@ -208,7 +209,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste
|
||||
add("refund", "failed", beginErr.Error(), req.TradeNo, refundStart)
|
||||
return test, beginErr
|
||||
}
|
||||
refund, refundErr := adapter.Refund(ctx, &system.PaymentRefundRequest{Provider: provider, TradeNo: req.TradeNo, ProviderTradeNo: order.ProviderTradeNo, QueryID: order.QueryID, RefundNo: order.RefundNo, Amount: req.Amount, TotalAmount: req.Amount, Currency: req.Currency}, values)
|
||||
refund, refundErr := adapter.Refund(ctx, &bizpayment.PaymentRefundRequest{Provider: provider, TradeNo: req.TradeNo, ProviderTradeNo: order.ProviderTradeNo, QueryID: order.QueryID, RefundNo: order.RefundNo, Amount: req.Amount, TotalAmount: req.Amount, Currency: req.Currency}, values)
|
||||
if refundErr != nil {
|
||||
recordPaymentTestError(ctx, r.data, provider, req.TradeNo, refundErr)
|
||||
add("refund", "failed", refundErr.Error(), req.TradeNo, refundStart)
|
||||
@@ -230,17 +231,17 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste
|
||||
return test, nil
|
||||
}
|
||||
|
||||
func paymentTestFingerprint(req *system.PaymentRequest) string {
|
||||
func paymentTestFingerprint(req *bizpayment.PaymentRequest) string {
|
||||
raw, _ := json.Marshal(req)
|
||||
hash := sha256.Sum256(raw)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func paymentTestProviderUpdate(result *system.PaymentResult) *system.PaymentProviderUpdate {
|
||||
func paymentTestProviderUpdate(result *bizpayment.PaymentResult) *bizpayment.PaymentProviderUpdate {
|
||||
if result == nil {
|
||||
return nil
|
||||
}
|
||||
return &system.PaymentProviderUpdate{
|
||||
return &bizpayment.PaymentProviderUpdate{
|
||||
Status: result.Status, ProviderStatus: result.Status, ProviderTradeNo: result.ProviderTradeNo, QueryID: result.QueryID,
|
||||
Amount: result.Amount, PayerPaidAmount: result.PayerPaidAmount, CashPaidAmount: result.CashPaidAmount,
|
||||
PointPaidAmount: result.PointPaidAmount, DiscountAmount: result.DiscountAmount,
|
||||
@@ -250,21 +251,21 @@ func paymentTestProviderUpdate(result *system.PaymentResult) *system.PaymentProv
|
||||
}
|
||||
}
|
||||
|
||||
func validatePaymentTestResult(provider, tradeNo string, result *system.PaymentResult) error {
|
||||
func validatePaymentTestResult(provider, tradeNo string, result *bizpayment.PaymentResult) error {
|
||||
if result == nil {
|
||||
return errors.New("支付渠道响应为空")
|
||||
}
|
||||
if strings.TrimSpace(result.Provider) != provider {
|
||||
return errors.New("支付渠道响应的 provider 不匹配")
|
||||
}
|
||||
if value := strings.TrimSpace(result.TradeNo); provider != system.PaymentApple && value != "" && value != tradeNo {
|
||||
if value := strings.TrimSpace(result.TradeNo); provider != bizpayment.PaymentApple && value != "" && value != tradeNo {
|
||||
return errors.New("支付渠道响应的商户订单号不匹配")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func queryPaymentTest(ctx context.Context, adapter datapayment.Adapter, queryID string, values map[string]any) (*system.PaymentResult, error) {
|
||||
var result *system.PaymentResult
|
||||
func queryPaymentTest(ctx context.Context, adapter datapayment.Adapter, queryID string, values map[string]any) (*bizpayment.PaymentResult, error) {
|
||||
var result *bizpayment.PaymentResult
|
||||
var err error
|
||||
for attempt := 0; attempt < 3; attempt++ {
|
||||
result, err = adapter.Query(ctx, queryID, values)
|
||||
@@ -300,7 +301,7 @@ func (r *paymentRepo) testRow(ctx context.Context, provider string) (map[string]
|
||||
var row integrationConfigPO
|
||||
if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, system.ErrPaymentProviderNotFound
|
||||
return nil, bizpayment.ErrPaymentProviderNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -311,17 +312,17 @@ func (r *paymentRepo) testRow(ctx context.Context, provider string) (map[string]
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func paymentTestRequest(provider string, values map[string]any) *system.PaymentRequest {
|
||||
func paymentTestRequest(provider string, values map[string]any) *bizpayment.PaymentRequest {
|
||||
tradeNo := "kra-test-" + time.Now().UTC().Format("20060102150405.000000000")
|
||||
amount := configuredInt64(values, "test_amount", 1)
|
||||
if amount <= 0 {
|
||||
amount = 1
|
||||
}
|
||||
req := &system.PaymentRequest{Provider: provider, TradeNo: strings.ReplaceAll(tradeNo, ".", ""), Subject: "Kra 支付渠道连通性测试", Amount: amount, Currency: strings.ToUpper(firstAny(values, "test_currency", "currency", "fee_type")), NotifyURL: text(values, "notify_url"), ReturnURL: text(values, "return_url"), BusinessType: "system_payment_test", BusinessID: uuid.NewString(), Extra: map[string]any{}}
|
||||
req := &bizpayment.PaymentRequest{Provider: provider, TradeNo: strings.ReplaceAll(tradeNo, ".", ""), Subject: "Kra 支付渠道连通性测试", Amount: amount, Currency: strings.ToUpper(firstAny(values, "test_currency", "currency", "fee_type")), NotifyURL: text(values, "notify_url"), ReturnURL: text(values, "return_url"), BusinessType: "system_payment_test", BusinessID: uuid.NewString(), Extra: map[string]any{}}
|
||||
if req.Currency == "" {
|
||||
req.Currency = "CNY"
|
||||
}
|
||||
if provider == system.PaymentApple {
|
||||
if provider == bizpayment.PaymentApple {
|
||||
req.TradeNo = uuid.NewString()
|
||||
req.Extra["product_id"] = firstAny(values, "product_id", "test_product_id")
|
||||
}
|
||||
@@ -351,10 +352,10 @@ func validatePaymentTestSettings(provider string, values map[string]any) error {
|
||||
return fmt.Errorf("test_extra 必须是 JSON 对象: %w", err)
|
||||
}
|
||||
}
|
||||
if provider == system.PaymentApple && strings.TrimSpace(text(values, "test_transaction_id")) == "" {
|
||||
if provider == bizpayment.PaymentApple && strings.TrimSpace(text(values, "test_transaction_id")) == "" {
|
||||
return errors.New("Apple 测试需要 test_transaction_id(沙箱交易 ID)")
|
||||
}
|
||||
if provider == system.PaymentApple && strings.TrimSpace(firstAny(values, "test_product_id", "product_id")) == "" {
|
||||
if provider == bizpayment.PaymentApple && strings.TrimSpace(firstAny(values, "test_product_id", "product_id")) == "" {
|
||||
return errors.New("Apple 测试需要 test_product_id(沙箱商品 ID)")
|
||||
}
|
||||
return nil
|
||||
@@ -377,7 +378,7 @@ func testModeEnabled(values map[string]any) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *paymentRepo) Create(ctx context.Context, req *system.PaymentRequest) (*system.PaymentResult, error) {
|
||||
func (r *paymentRepo) Create(ctx context.Context, req *bizpayment.PaymentRequest) (*bizpayment.PaymentResult, error) {
|
||||
if req == nil {
|
||||
return nil, errors.New("支付下单请求为空")
|
||||
}
|
||||
@@ -396,7 +397,7 @@ func (r *paymentRepo) Create(ctx context.Context, req *system.PaymentRequest) (*
|
||||
|
||||
func paymentProviderRequiresNotifyURL(provider string) bool {
|
||||
switch provider {
|
||||
case system.PaymentApple, system.PaymentAllinPay, system.PaymentSaobei, system.PaymentPayPal:
|
||||
case bizpayment.PaymentApple, bizpayment.PaymentAllinPay, bizpayment.PaymentSaobei, bizpayment.PaymentPayPal:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
@@ -413,15 +414,15 @@ func paymentCreateRequiresNotifyURL(provider string, extra, config map[string]an
|
||||
}
|
||||
keys := []string{"method", "pay_method", "trade_type", "pay_type", "channel"}
|
||||
switch provider {
|
||||
case system.PaymentAlipay, system.PaymentAlipayV3:
|
||||
case bizpayment.PaymentAlipay, bizpayment.PaymentAlipayV3:
|
||||
keys = []string{"method", "pay_method", "trade_type", "channel"}
|
||||
case system.PaymentWechatV2:
|
||||
case bizpayment.PaymentWechatV2:
|
||||
keys = []string{"trade_type", "pay_type", "method", "pay_method", "channel"}
|
||||
case system.PaymentWechatV3:
|
||||
case bizpayment.PaymentWechatV3:
|
||||
keys = []string{"trade_type", "pay_type", "method"}
|
||||
case system.PaymentQQ:
|
||||
case bizpayment.PaymentQQ:
|
||||
keys = []string{"trade_type", "pay_type", "method", "pay_method"}
|
||||
case system.PaymentLakala:
|
||||
case bizpayment.PaymentLakala:
|
||||
keys = []string{"method", "pay_method", "trade_type"}
|
||||
}
|
||||
value := firstAny(extra, keys...)
|
||||
@@ -430,28 +431,28 @@ func paymentCreateRequiresNotifyURL(provider string, extra, config map[string]an
|
||||
}
|
||||
normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
|
||||
switch provider {
|
||||
case system.PaymentAlipay, system.PaymentAlipayV3:
|
||||
case bizpayment.PaymentAlipay, bizpayment.PaymentAlipayV3:
|
||||
return !contains([]string{"pay", "trade_pay", "alipay_trade_pay", "barcode", "barcode_pay", "micropay", "face_to_face"}, normalized)
|
||||
case system.PaymentWechatV2:
|
||||
case bizpayment.PaymentWechatV2:
|
||||
return !contains([]string{"micropay", "micro_pay", "barcode", "barcode_pay", "pay_code", "payment_code"}, normalized)
|
||||
case system.PaymentWechatV3:
|
||||
case bizpayment.PaymentWechatV3:
|
||||
return !contains([]string{"micropay", "micro_pay", "codepay", "code_pay", "barcode", "barcode_pay", "facepay", "face_pay"}, normalized)
|
||||
case system.PaymentQQ:
|
||||
case bizpayment.PaymentQQ:
|
||||
return !contains([]string{"micropay", "micro_pay", "barcode", "barcode_pay"}, normalized)
|
||||
case system.PaymentLakala:
|
||||
case bizpayment.PaymentLakala:
|
||||
return !contains([]string{"retail", "retail_pay", "micropay", "barcode"}, normalized)
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
func (r *paymentRepo) Query(ctx context.Context, provider, tradeNo string) (*system.PaymentResult, error) {
|
||||
func (r *paymentRepo) Query(ctx context.Context, provider, tradeNo string) (*bizpayment.PaymentResult, error) {
|
||||
a, c, err := r.adapter(ctx, provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.Query(ctx, tradeNo, c)
|
||||
}
|
||||
func (r *paymentRepo) Refund(ctx context.Context, req *system.PaymentRefundRequest) (*system.PaymentResult, error) {
|
||||
func (r *paymentRepo) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest) (*bizpayment.PaymentResult, error) {
|
||||
if req == nil {
|
||||
return nil, errors.New("支付退款请求为空")
|
||||
}
|
||||
@@ -461,7 +462,7 @@ func (r *paymentRepo) Refund(ctx context.Context, req *system.PaymentRefundReque
|
||||
}
|
||||
return a.Refund(ctx, req, c)
|
||||
}
|
||||
func (r *paymentRepo) HandleCallback(ctx context.Context, callback *system.PaymentCallback) (*system.PaymentResult, error) {
|
||||
func (r *paymentRepo) HandleCallback(ctx context.Context, callback *bizpayment.PaymentCallback) (*bizpayment.PaymentResult, error) {
|
||||
if callback == nil {
|
||||
return nil, errors.New("支付回调为空")
|
||||
}
|
||||
@@ -471,23 +472,23 @@ func (r *paymentRepo) HandleCallback(ctx context.Context, callback *system.Payme
|
||||
}
|
||||
result, err := a.Callback(ctx, callback, c)
|
||||
if err != nil {
|
||||
return nil, &system.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)}
|
||||
return nil, &bizpayment.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)}
|
||||
}
|
||||
if result == nil {
|
||||
err = errors.New("支付回调解析结果为空")
|
||||
return nil, &system.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)}
|
||||
return nil, &bizpayment.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)}
|
||||
}
|
||||
result.SuccessAck = paymentCallbackAck(callback.Provider, c, true)
|
||||
result.FailureAck = paymentCallbackAck(callback.Provider, c, false)
|
||||
if result.Provider != callback.Provider {
|
||||
err = errors.New("支付回调渠道不匹配")
|
||||
return nil, &system.PaymentCallbackError{Cause: err, Ack: result.FailureAck}
|
||||
return nil, &bizpayment.PaymentCallbackError{Cause: err, Ack: result.FailureAck}
|
||||
}
|
||||
result.EventID = paymentCallbackEventID(callback, result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func paymentCallbackEventID(callback *system.PaymentCallback, result *system.PaymentResult) string {
|
||||
func paymentCallbackEventID(callback *bizpayment.PaymentCallback, result *bizpayment.PaymentResult) string {
|
||||
if result != nil {
|
||||
if eventID := strings.TrimSpace(result.EventID); eventID != "" {
|
||||
return eventID
|
||||
@@ -504,8 +505,8 @@ func paymentCallbackEventID(callback *system.PaymentCallback, result *system.Pay
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func paymentCallbackAck(provider string, values map[string]any, success bool) system.PaymentCallbackAck {
|
||||
ack := system.DefaultPaymentCallbackAck(provider, success)
|
||||
func paymentCallbackAck(provider string, values map[string]any, success bool) bizpayment.PaymentCallbackAck {
|
||||
ack := bizpayment.DefaultPaymentCallbackAck(provider, success)
|
||||
prefix := "callback_success_"
|
||||
if !success {
|
||||
prefix = "callback_failure_"
|
||||
@@ -524,7 +525,7 @@ func paymentCallbackAck(provider string, values map[string]any, success bool) sy
|
||||
return ack
|
||||
}
|
||||
|
||||
func callbackFields(callback *system.PaymentCallback) map[string]string {
|
||||
func callbackFields(callback *bizpayment.PaymentCallback) map[string]string {
|
||||
fields := map[string]string{}
|
||||
for key, value := range callback.Query {
|
||||
fields[key] = value
|
||||
@@ -569,5 +570,5 @@ func contains(values []string, value string) bool {
|
||||
}
|
||||
|
||||
func validatePaymentConfig(provider string, values map[string]any) error {
|
||||
return system.ValidateIntegrationConfig(system.IntegrationKindPayment, provider, values)
|
||||
return integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, provider, values)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"kra/internal/biz/system"
|
||||
integrationbiz "kra/internal/biz/integration"
|
||||
bizpayment "kra/internal/biz/payment"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -11,18 +12,18 @@ func TestValidatePaymentConfigRequiresDouyinAppIDWhenEnabled(t *testing.T) {
|
||||
"merchant_id": "merchant-douyin", "serial_no": "merchant-serial", "api_key": "01234567890123456789012345678901",
|
||||
"private_key": "merchant-private-key", "platform_cert": "platform-public-key", "platform_serial_no": "platform-serial",
|
||||
}
|
||||
err := system.ValidateIntegrationConfig(system.IntegrationKindPayment, system.PaymentDouyin, values)
|
||||
err := integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, bizpayment.PaymentDouyin, values)
|
||||
if err == nil || !strings.Contains(err.Error(), "app_id") {
|
||||
t.Fatalf("missing app_id error = %v", err)
|
||||
}
|
||||
values["app_id"] = "douyin-app"
|
||||
if err = system.ValidateIntegrationConfig(system.IntegrationKindPayment, system.PaymentDouyin, values); err != nil {
|
||||
if err = integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, bizpayment.PaymentDouyin, values); err != nil {
|
||||
t.Fatalf("valid Douyin configuration rejected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePaymentConfigAcceptsProviderAliases(t *testing.T) {
|
||||
err := validatePaymentConfig(system.PaymentDouyin, map[string]any{
|
||||
err := validatePaymentConfig(bizpayment.PaymentDouyin, map[string]any{
|
||||
"app_id": "douyin-app", "merchant_id": "merchant-douyin", "serial_no": "merchant-serial", "api_key": "01234567890123456789012345678901",
|
||||
"private_key": "merchant-private-key", "platform_cert": "platform-public-key", "platform_cert_serial": "platform-serial",
|
||||
})
|
||||
@@ -36,13 +37,13 @@ func TestValidatePaymentConfigProviderRules(t *testing.T) {
|
||||
name, provider, want string
|
||||
values map[string]any
|
||||
}{
|
||||
{"allinpay order type", system.PaymentAllinPay, "reqsn", map[string]any{"cus_id": "customer", "app_id": "app", "private_key": "private-key", "public_key": "public-key", "query_order_type": "payinfo"}},
|
||||
{"paypal webhook", system.PaymentPayPal, "webhook_id", map[string]any{"client_id": "client-id", "client_secret": "client-secret"}},
|
||||
{"wechat v2 refund cert", system.PaymentWechatV2, "client_cert", map[string]any{"app_id": "app", "merchant_id": "merchant", "mch_key": "key"}},
|
||||
{"allinpay order type", bizpayment.PaymentAllinPay, "reqsn", map[string]any{"cus_id": "customer", "app_id": "app", "private_key": "private-key", "public_key": "public-key", "query_order_type": "payinfo"}},
|
||||
{"paypal webhook", bizpayment.PaymentPayPal, "webhook_id", map[string]any{"client_id": "client-id", "client_secret": "client-secret"}},
|
||||
{"wechat v2 refund cert", bizpayment.PaymentWechatV2, "client_cert", map[string]any{"app_id": "app", "merchant_id": "merchant", "mch_key": "key"}},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := system.ValidateIntegrationConfig(system.IntegrationKindPayment, test.provider, test.values)
|
||||
err := integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, test.provider, test.values)
|
||||
if err == nil || !strings.Contains(err.Error(), test.want) {
|
||||
t.Fatalf("error = %v, want %q", err, test.want)
|
||||
}
|
||||
@@ -51,13 +52,13 @@ func TestValidatePaymentConfigProviderRules(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidatePaymentConfigGenericRequiresRuntimeFields(t *testing.T) {
|
||||
values := system.DefaultIntegrationConfig(system.IntegrationKindPayment, system.PaymentChinaums)
|
||||
if err := system.ValidateIntegrationConfig(system.IntegrationKindPayment, system.PaymentChinaums, values); err == nil {
|
||||
values := integrationbiz.DefaultIntegrationConfig(integrationbiz.IntegrationKindPayment, bizpayment.PaymentChinaums)
|
||||
if err := integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, bizpayment.PaymentChinaums, values); err == nil {
|
||||
t.Fatal("empty generic payment config unexpectedly accepted")
|
||||
}
|
||||
values["app_id"], values["merchant_id"] = "app", "merchant"
|
||||
values["create_url"], values["query_url"], values["refund_url"], values["app_key"] = "https://pay.test/create", "https://pay.test/query", "https://pay.test/refund", "secret"
|
||||
if err := system.ValidateIntegrationConfig(system.IntegrationKindPayment, system.PaymentChinaums, values); err == nil {
|
||||
if err := integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, bizpayment.PaymentChinaums, values); err == nil {
|
||||
t.Fatal("generic config with only identity/endpoints unexpectedly accepted")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"kra/internal/biz/system"
|
||||
integrationbiz "kra/internal/biz/integration"
|
||||
bizpayment "kra/internal/biz/payment"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPaymentDefinitionsProvideNonEmptyDefaults(t *testing.T) {
|
||||
definitions := system.IntegrationDefinitions(system.IntegrationKindPayment)
|
||||
if len(definitions) != len(system.SupportedPaymentProviders) {
|
||||
t.Fatalf("payment definitions = %d, want %d", len(definitions), len(system.SupportedPaymentProviders))
|
||||
definitions := integrationbiz.IntegrationDefinitions(integrationbiz.IntegrationKindPayment)
|
||||
if len(definitions) != len(bizpayment.SupportedPaymentProviders) {
|
||||
t.Fatalf("payment definitions = %d, want %d", len(definitions), len(bizpayment.SupportedPaymentProviders))
|
||||
}
|
||||
for _, definition := range definitions {
|
||||
if definition.Provider == "" || definition.Name == "" || len(definition.Fields) == 0 {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"kra/internal/biz/system"
|
||||
bizpayment "kra/internal/biz/payment"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -68,11 +68,11 @@ func (paymentOrderPO) TableName() string { return "pay_orders" }
|
||||
|
||||
type paymentOrderRepo struct{ data Provider }
|
||||
|
||||
func NewPaymentOrderRepo(data Provider) system.PaymentOrderRepo {
|
||||
func NewPaymentOrderRepo(data Provider) bizpayment.PaymentOrderRepo {
|
||||
return &paymentOrderRepo{data: data}
|
||||
}
|
||||
|
||||
func newPaymentOrderPO(order *system.PaymentOrder) (*paymentOrderPO, error) {
|
||||
func newPaymentOrderPO(order *bizpayment.PaymentOrder) (*paymentOrderPO, error) {
|
||||
if order == nil {
|
||||
return nil, errors.New("支付订单为空")
|
||||
}
|
||||
@@ -85,16 +85,16 @@ func newPaymentOrderPO(order *system.PaymentOrder) (*paymentOrderPO, error) {
|
||||
ID: order.ID, TradeNo: order.TradeNo, Provider: order.Provider,
|
||||
ProviderTradeNo: optionalString(order.ProviderTradeNo), QueryID: order.QueryID,
|
||||
BusinessType: order.BusinessType, BusinessID: order.BusinessID, Subject: order.Subject,
|
||||
PaymentMode: defaultString(order.PaymentMode, system.PaymentModeExternal), OriginalAmount: order.OriginalAmount,
|
||||
PaymentMode: defaultString(order.PaymentMode, bizpayment.PaymentModeExternal), OriginalAmount: order.OriginalAmount,
|
||||
Amount: order.Amount, PaidAmount: order.PaidAmount, PayerPaidAmount: order.PayerPaidAmount,
|
||||
CashPaidAmount: order.CashPaidAmount, PointPaidAmount: order.PointPaidAmount, DiscountAmount: order.DiscountAmount,
|
||||
ProviderDiscountAmount: order.ProviderDiscountAmount, MerchantDiscountAmount: order.MerchantDiscountAmount,
|
||||
SettlementAmount: order.SettlementAmount, Currency: order.Currency, PayerCurrency: order.PayerCurrency,
|
||||
AmountBreakdownKnown: order.AmountBreakdownKnown,
|
||||
PaymentStatus: defaultString(order.PaymentStatus, system.PaymentStatusInitialized),
|
||||
PaymentStatus: defaultString(order.PaymentStatus, bizpayment.PaymentStatusInitialized),
|
||||
ProviderStatus: order.ProviderStatus,
|
||||
FulfillmentStatus: defaultString(order.FulfillmentStatus, system.FulfillmentStatusPending),
|
||||
RefundStatus: defaultString(order.RefundStatus, system.RefundStatusNone),
|
||||
FulfillmentStatus: defaultString(order.FulfillmentStatus, bizpayment.FulfillmentStatusPending),
|
||||
RefundStatus: defaultString(order.RefundStatus, bizpayment.RefundStatusNone),
|
||||
RefundedAmount: order.RefundedAmount, RefundRequestedAmount: order.RefundRequestedAmount, RefundNo: order.RefundNo,
|
||||
ConfirmationID: order.ConfirmationID, RequestFingerprint: order.RequestFingerprint,
|
||||
CreatePayload: createPayload, Extra: extra, LastEventID: order.LastEventID,
|
||||
@@ -105,15 +105,15 @@ func newPaymentOrderPO(order *system.PaymentOrder) (*paymentOrderPO, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toBizPaymentOrder(po *paymentOrderPO) *system.PaymentOrder {
|
||||
func toBizPaymentOrder(po *paymentOrderPO) *bizpayment.PaymentOrder {
|
||||
if po == nil {
|
||||
return nil
|
||||
}
|
||||
return &system.PaymentOrder{
|
||||
return &bizpayment.PaymentOrder{
|
||||
ID: po.ID, TradeNo: po.TradeNo, Provider: po.Provider,
|
||||
ProviderTradeNo: dereferenceString(po.ProviderTradeNo), QueryID: po.QueryID,
|
||||
BusinessType: po.BusinessType, BusinessID: po.BusinessID, Subject: po.Subject,
|
||||
PaymentMode: defaultString(po.PaymentMode, system.PaymentModeExternal), OriginalAmount: po.OriginalAmount,
|
||||
PaymentMode: defaultString(po.PaymentMode, bizpayment.PaymentModeExternal), OriginalAmount: po.OriginalAmount,
|
||||
Amount: po.Amount, PaidAmount: po.PaidAmount, PayerPaidAmount: po.PayerPaidAmount,
|
||||
CashPaidAmount: po.CashPaidAmount, PointPaidAmount: po.PointPaidAmount, DiscountAmount: po.DiscountAmount,
|
||||
ProviderDiscountAmount: po.ProviderDiscountAmount, MerchantDiscountAmount: po.MerchantDiscountAmount,
|
||||
@@ -131,7 +131,7 @@ func toBizPaymentOrder(po *paymentOrderPO) *system.PaymentOrder {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *paymentOrderRepo) CreatePaymentOrder(ctx context.Context, order *system.PaymentOrder) (*system.PaymentOrder, bool, error) {
|
||||
func (r *paymentOrderRepo) CreatePaymentOrder(ctx context.Context, order *bizpayment.PaymentOrder) (*bizpayment.PaymentOrder, bool, error) {
|
||||
po, err := newPaymentOrderPO(order)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
@@ -154,18 +154,18 @@ func (r *paymentOrderRepo) CreatePaymentOrder(ctx context.Context, order *system
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
func (r *paymentOrderRepo) FindPaymentOrder(ctx context.Context, provider, tradeNo string) (*system.PaymentOrder, error) {
|
||||
func (r *paymentOrderRepo) FindPaymentOrder(ctx context.Context, provider, tradeNo string) (*bizpayment.PaymentOrder, error) {
|
||||
var po paymentOrderPO
|
||||
if err := r.data.DB().WithContext(ctx).Where("provider = ? AND trade_no = ?", provider, tradeNo).First(&po).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, system.ErrPaymentOrderNotFound
|
||||
return nil, bizpayment.ErrPaymentOrderNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return toBizPaymentOrder(&po), nil
|
||||
}
|
||||
|
||||
func (r *paymentOrderRepo) ListPaymentOrders(ctx context.Context, page, pageSize int, filter system.PaymentOrderFilter) ([]*system.PaymentOrder, int64, error) {
|
||||
func (r *paymentOrderRepo) ListPaymentOrders(ctx context.Context, page, pageSize int, filter bizpayment.PaymentOrderFilter) ([]*bizpayment.PaymentOrder, int64, error) {
|
||||
db := r.data.DB().WithContext(ctx).Model(&paymentOrderPO{})
|
||||
if value := strings.TrimSpace(filter.Provider); value != "" {
|
||||
db = db.Where("provider = ?", value)
|
||||
@@ -193,14 +193,14 @@ func (r *paymentOrderRepo) ListPaymentOrders(ctx context.Context, page, pageSize
|
||||
if err := pagination.ApplyRequired(db.Order("id desc"), page, pageSize, 100).Find(&rows).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
items := make([]*system.PaymentOrder, 0, len(rows))
|
||||
items := make([]*bizpayment.PaymentOrder, 0, len(rows))
|
||||
for i := range rows {
|
||||
items = append(items, toBizPaymentOrder(&rows[i]))
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func (r *paymentOrderRepo) RecordPaymentCreate(ctx context.Context, provider, tradeNo string, update *system.PaymentProviderUpdate) (*system.PaymentOrder, error) {
|
||||
func (r *paymentOrderRepo) RecordPaymentCreate(ctx context.Context, provider, tradeNo string, update *bizpayment.PaymentProviderUpdate) (*bizpayment.PaymentOrder, error) {
|
||||
return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error {
|
||||
if update == nil {
|
||||
return errors.New("支付下单结果为空")
|
||||
@@ -215,11 +215,11 @@ func (r *paymentOrderRepo) RecordPaymentCreate(ctx context.Context, provider, tr
|
||||
po.LastEventID = trimTo(update.EventID, 128)
|
||||
po.LastPayloadHash = trimTo(update.PayloadHash, 64)
|
||||
status := normalizeOrderPaymentStatus(update.Status)
|
||||
if status == system.PaymentStatusPaid {
|
||||
if status == bizpayment.PaymentStatusPaid {
|
||||
// Provider create responses are never sufficient proof of payment.
|
||||
status = system.PaymentStatusPending
|
||||
status = bizpayment.PaymentStatusPending
|
||||
}
|
||||
if po.PaymentStatus != system.PaymentStatusPaid && po.PaymentStatus != system.PaymentStatusPartiallyRefunded && po.PaymentStatus != system.PaymentStatusRefunded && status != "" {
|
||||
if po.PaymentStatus != bizpayment.PaymentStatusPaid && po.PaymentStatus != bizpayment.PaymentStatusPartiallyRefunded && po.PaymentStatus != bizpayment.PaymentStatusRefunded && status != "" {
|
||||
po.PaymentStatus = status
|
||||
}
|
||||
po.Version++
|
||||
@@ -227,7 +227,7 @@ func (r *paymentOrderRepo) RecordPaymentCreate(ctx context.Context, provider, tr
|
||||
})
|
||||
}
|
||||
|
||||
func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tradeNo string, update *system.PaymentProviderUpdate) (*system.PaymentOrder, error) {
|
||||
func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tradeNo string, update *bizpayment.PaymentProviderUpdate) (*bizpayment.PaymentOrder, error) {
|
||||
return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error {
|
||||
if update == nil {
|
||||
return errors.New("支付查单结果为空")
|
||||
@@ -236,13 +236,13 @@ func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tra
|
||||
return err
|
||||
}
|
||||
if update.Amount > 0 && update.Amount != po.Amount {
|
||||
return system.ErrPaymentOrderConflict
|
||||
return bizpayment.ErrPaymentOrderConflict
|
||||
}
|
||||
if update.PayerPaidAmount < 0 || update.CashPaidAmount < 0 || update.PointPaidAmount < 0 || update.DiscountAmount < 0 || update.ProviderDiscountAmount < 0 || update.MerchantDiscountAmount < 0 || update.SettlementAmount < 0 {
|
||||
return system.ErrPaymentOrderConflict
|
||||
return bizpayment.ErrPaymentOrderConflict
|
||||
}
|
||||
if update.Currency != "" && !strings.EqualFold(update.Currency, po.Currency) {
|
||||
return system.ErrPaymentOrderConflict
|
||||
return bizpayment.ErrPaymentOrderConflict
|
||||
}
|
||||
po.ProviderStatus = trimTo(update.ProviderStatus, 64)
|
||||
po.LastEventID = trimTo(update.EventID, 128)
|
||||
@@ -259,22 +259,22 @@ func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tra
|
||||
po.AmountBreakdownKnown = true
|
||||
}
|
||||
switch normalizeOrderPaymentStatus(update.Status) {
|
||||
case system.PaymentStatusPaid:
|
||||
if po.PaymentStatus != system.PaymentStatusPartiallyRefunded && po.PaymentStatus != system.PaymentStatusRefunded {
|
||||
po.PaymentStatus = system.PaymentStatusPaid
|
||||
case bizpayment.PaymentStatusPaid:
|
||||
if po.PaymentStatus != bizpayment.PaymentStatusPartiallyRefunded && po.PaymentStatus != bizpayment.PaymentStatusRefunded {
|
||||
po.PaymentStatus = bizpayment.PaymentStatusPaid
|
||||
}
|
||||
po.PaidAmount = po.Amount
|
||||
if po.PaidAt == nil {
|
||||
now := time.Now().UTC()
|
||||
po.PaidAt = &now
|
||||
}
|
||||
case system.PaymentStatusPending:
|
||||
if po.PaymentStatus != system.PaymentStatusPaid && po.PaymentStatus != system.PaymentStatusPartiallyRefunded && po.PaymentStatus != system.PaymentStatusRefunded {
|
||||
po.PaymentStatus = system.PaymentStatusPending
|
||||
case bizpayment.PaymentStatusPending:
|
||||
if po.PaymentStatus != bizpayment.PaymentStatusPaid && po.PaymentStatus != bizpayment.PaymentStatusPartiallyRefunded && po.PaymentStatus != bizpayment.PaymentStatusRefunded {
|
||||
po.PaymentStatus = bizpayment.PaymentStatusPending
|
||||
}
|
||||
case system.PaymentStatusFailed:
|
||||
if po.PaymentStatus != system.PaymentStatusPaid && po.PaymentStatus != system.PaymentStatusPartiallyRefunded && po.PaymentStatus != system.PaymentStatusRefunded {
|
||||
po.PaymentStatus = system.PaymentStatusFailed
|
||||
case bizpayment.PaymentStatusFailed:
|
||||
if po.PaymentStatus != bizpayment.PaymentStatusPaid && po.PaymentStatus != bizpayment.PaymentStatusPartiallyRefunded && po.PaymentStatus != bizpayment.PaymentStatusRefunded {
|
||||
po.PaymentStatus = bizpayment.PaymentStatusFailed
|
||||
}
|
||||
}
|
||||
po.Version++
|
||||
@@ -282,27 +282,27 @@ func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tra
|
||||
})
|
||||
}
|
||||
|
||||
func (r *paymentOrderRepo) BeginPaymentFulfillment(ctx context.Context, provider, tradeNo string, lease time.Duration) (*system.PaymentOrder, string, bool, error) {
|
||||
func (r *paymentOrderRepo) BeginPaymentFulfillment(ctx context.Context, provider, tradeNo string, lease time.Duration) (*bizpayment.PaymentOrder, string, bool, error) {
|
||||
var token string
|
||||
var duplicate bool
|
||||
order, err := r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error {
|
||||
if po.FulfillmentStatus == system.FulfillmentStatusSucceeded {
|
||||
if po.FulfillmentStatus == bizpayment.FulfillmentStatusSucceeded {
|
||||
duplicate = true
|
||||
return nil
|
||||
}
|
||||
if po.PaymentStatus != system.PaymentStatusPaid {
|
||||
return system.ErrPaymentOrderState
|
||||
if po.PaymentStatus != bizpayment.PaymentStatusPaid {
|
||||
return bizpayment.ErrPaymentOrderState
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
if po.FulfillmentStatus == system.FulfillmentStatusProcessing && po.FulfillmentLeaseUntil != nil && po.FulfillmentLeaseUntil.After(now) {
|
||||
return system.ErrPaymentOrderBusy
|
||||
if po.FulfillmentStatus == bizpayment.FulfillmentStatusProcessing && po.FulfillmentLeaseUntil != nil && po.FulfillmentLeaseUntil.After(now) {
|
||||
return bizpayment.ErrPaymentOrderBusy
|
||||
}
|
||||
if lease <= 0 {
|
||||
lease = 10 * time.Minute
|
||||
}
|
||||
token = uuid.NewString()
|
||||
until := now.Add(lease)
|
||||
po.FulfillmentStatus = system.FulfillmentStatusProcessing
|
||||
po.FulfillmentStatus = bizpayment.FulfillmentStatusProcessing
|
||||
po.FulfillmentToken = token
|
||||
po.FulfillmentLeaseUntil = &until
|
||||
po.LastError = ""
|
||||
@@ -312,56 +312,56 @@ func (r *paymentOrderRepo) BeginPaymentFulfillment(ctx context.Context, provider
|
||||
return order, token, duplicate, err
|
||||
}
|
||||
|
||||
func (r *paymentOrderRepo) CompletePaymentFulfillment(ctx context.Context, provider, tradeNo, token string, success bool, message string) (*system.PaymentOrder, error) {
|
||||
func (r *paymentOrderRepo) CompletePaymentFulfillment(ctx context.Context, provider, tradeNo, token string, success bool, message string) (*bizpayment.PaymentOrder, error) {
|
||||
return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error {
|
||||
if po.FulfillmentStatus != system.FulfillmentStatusProcessing || po.FulfillmentToken != token {
|
||||
return system.ErrPaymentOrderBusy
|
||||
if po.FulfillmentStatus != bizpayment.FulfillmentStatusProcessing || po.FulfillmentToken != token {
|
||||
return bizpayment.ErrPaymentOrderBusy
|
||||
}
|
||||
po.FulfillmentToken = ""
|
||||
po.FulfillmentLeaseUntil = nil
|
||||
po.LastError = trimTo(message, 512)
|
||||
if success {
|
||||
po.FulfillmentStatus = system.FulfillmentStatusSucceeded
|
||||
po.FulfillmentStatus = bizpayment.FulfillmentStatusSucceeded
|
||||
now := time.Now().UTC()
|
||||
po.FulfilledAt = &now
|
||||
} else {
|
||||
po.FulfillmentStatus = system.FulfillmentStatusFailed
|
||||
po.FulfillmentStatus = bizpayment.FulfillmentStatusFailed
|
||||
}
|
||||
po.Version++
|
||||
return tx.Save(po).Error
|
||||
})
|
||||
}
|
||||
|
||||
func (r *paymentOrderRepo) BeginPaymentRefund(ctx context.Context, provider, tradeNo string, amount int64, lease time.Duration) (*system.PaymentOrder, string, error) {
|
||||
func (r *paymentOrderRepo) BeginPaymentRefund(ctx context.Context, provider, tradeNo string, amount int64, lease time.Duration) (*bizpayment.PaymentOrder, string, error) {
|
||||
var token string
|
||||
order, err := r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error {
|
||||
if po.PaymentStatus != system.PaymentStatusPaid && po.PaymentStatus != system.PaymentStatusPartiallyRefunded {
|
||||
return system.ErrPaymentOrderState
|
||||
if po.PaymentStatus != bizpayment.PaymentStatusPaid && po.PaymentStatus != bizpayment.PaymentStatusPartiallyRefunded {
|
||||
return bizpayment.ErrPaymentOrderState
|
||||
}
|
||||
if amount <= 0 {
|
||||
return system.ErrPaymentOrderConflict
|
||||
return bizpayment.ErrPaymentOrderConflict
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
if po.RefundStatus == system.RefundStatusProcessing && po.RefundLeaseUntil != nil && po.RefundLeaseUntil.After(now) {
|
||||
return system.ErrPaymentOrderBusy
|
||||
if po.RefundStatus == bizpayment.RefundStatusProcessing && po.RefundLeaseUntil != nil && po.RefundLeaseUntil.After(now) {
|
||||
return bizpayment.ErrPaymentOrderBusy
|
||||
}
|
||||
if po.RefundStatus == system.RefundStatusProcessing && po.RefundRequestedAmount != amount {
|
||||
return system.ErrPaymentOrderConflict
|
||||
if po.RefundStatus == bizpayment.RefundStatusProcessing && po.RefundRequestedAmount != amount {
|
||||
return bizpayment.ErrPaymentOrderConflict
|
||||
}
|
||||
if po.RefundStatus == system.RefundStatusPending {
|
||||
return system.ErrPaymentOrderBusy
|
||||
if po.RefundStatus == bizpayment.RefundStatusPending {
|
||||
return bizpayment.ErrPaymentOrderBusy
|
||||
}
|
||||
// An expired processing lease means the provider outcome is unknown.
|
||||
// Retry the same refund amount with the same durable refund number. A
|
||||
// different amount must never reuse that operation identity.
|
||||
reserved := int64(0)
|
||||
if po.RefundStatus == system.RefundStatusProcessing && po.RefundLeaseUntil != nil && !po.RefundLeaseUntil.After(now) {
|
||||
if po.RefundStatus == bizpayment.RefundStatusProcessing && po.RefundLeaseUntil != nil && !po.RefundLeaseUntil.After(now) {
|
||||
reserved = 0
|
||||
} else {
|
||||
reserved = po.RefundRequestedAmount
|
||||
}
|
||||
if amount > po.Amount-po.RefundedAmount-reserved {
|
||||
return system.ErrPaymentOrderConflict
|
||||
return bizpayment.ErrPaymentOrderConflict
|
||||
}
|
||||
if lease <= 0 {
|
||||
lease = 10 * time.Minute
|
||||
@@ -371,7 +371,7 @@ func (r *paymentOrderRepo) BeginPaymentRefund(ctx context.Context, provider, tra
|
||||
po.RefundNo = uuid.NewString()
|
||||
}
|
||||
until := now.Add(lease)
|
||||
po.RefundStatus = system.RefundStatusProcessing
|
||||
po.RefundStatus = bizpayment.RefundStatusProcessing
|
||||
po.RefundRequestedAmount = amount
|
||||
po.RefundToken = token
|
||||
po.RefundLeaseUntil = &until
|
||||
@@ -382,18 +382,18 @@ func (r *paymentOrderRepo) BeginPaymentRefund(ctx context.Context, provider, tra
|
||||
return order, token, err
|
||||
}
|
||||
|
||||
func (r *paymentOrderRepo) CompletePaymentRefundRequest(ctx context.Context, provider, tradeNo, token string, accepted bool, message string) (*system.PaymentOrder, error) {
|
||||
func (r *paymentOrderRepo) CompletePaymentRefundRequest(ctx context.Context, provider, tradeNo, token string, accepted bool, message string) (*bizpayment.PaymentOrder, error) {
|
||||
return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error {
|
||||
if po.RefundStatus != system.RefundStatusProcessing || po.RefundToken != token {
|
||||
return system.ErrPaymentOrderBusy
|
||||
if po.RefundStatus != bizpayment.RefundStatusProcessing || po.RefundToken != token {
|
||||
return bizpayment.ErrPaymentOrderBusy
|
||||
}
|
||||
po.RefundToken = ""
|
||||
po.RefundLeaseUntil = nil
|
||||
po.LastError = trimTo(message, 512)
|
||||
if accepted {
|
||||
po.RefundStatus = system.RefundStatusPending
|
||||
po.RefundStatus = bizpayment.RefundStatusPending
|
||||
} else {
|
||||
po.RefundStatus = system.RefundStatusFailed
|
||||
po.RefundStatus = bizpayment.RefundStatusFailed
|
||||
po.RefundRequestedAmount = 0
|
||||
po.RefundNo = ""
|
||||
}
|
||||
@@ -402,26 +402,26 @@ func (r *paymentOrderRepo) CompletePaymentRefundRequest(ctx context.Context, pro
|
||||
})
|
||||
}
|
||||
|
||||
func (r *paymentOrderRepo) ConfirmPaymentRefund(ctx context.Context, provider, tradeNo, refundNo string, amount int64, success bool, message string) (*system.PaymentOrder, error) {
|
||||
func (r *paymentOrderRepo) ConfirmPaymentRefund(ctx context.Context, provider, tradeNo, refundNo string, amount int64, success bool, message string) (*bizpayment.PaymentOrder, error) {
|
||||
return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error {
|
||||
if po.RefundStatus != system.RefundStatusPending || po.RefundNo == "" || po.RefundNo != refundNo || po.RefundRequestedAmount != amount {
|
||||
return system.ErrPaymentOrderState
|
||||
if po.RefundStatus != bizpayment.RefundStatusPending || po.RefundNo == "" || po.RefundNo != refundNo || po.RefundRequestedAmount != amount {
|
||||
return bizpayment.ErrPaymentOrderState
|
||||
}
|
||||
po.LastError = trimTo(message, 512)
|
||||
po.RefundRequestedAmount = 0
|
||||
if !success {
|
||||
po.RefundStatus = system.RefundStatusFailed
|
||||
po.RefundStatus = bizpayment.RefundStatusFailed
|
||||
po.RefundNo = ""
|
||||
po.Version++
|
||||
return tx.Save(po).Error
|
||||
}
|
||||
po.RefundedAmount += amount
|
||||
if po.RefundedAmount >= po.Amount {
|
||||
po.PaymentStatus = system.PaymentStatusRefunded
|
||||
po.RefundStatus = system.RefundStatusSucceeded
|
||||
po.PaymentStatus = bizpayment.PaymentStatusRefunded
|
||||
po.RefundStatus = bizpayment.RefundStatusSucceeded
|
||||
} else {
|
||||
po.PaymentStatus = system.PaymentStatusPartiallyRefunded
|
||||
po.RefundStatus = system.RefundStatusPartial
|
||||
po.PaymentStatus = bizpayment.PaymentStatusPartiallyRefunded
|
||||
po.RefundStatus = bizpayment.RefundStatusPartial
|
||||
}
|
||||
po.RefundNo = ""
|
||||
now := time.Now().UTC()
|
||||
@@ -431,13 +431,13 @@ func (r *paymentOrderRepo) ConfirmPaymentRefund(ctx context.Context, provider, t
|
||||
})
|
||||
}
|
||||
|
||||
func (r *paymentOrderRepo) withLockedOrder(ctx context.Context, provider, tradeNo string, fn func(*gorm.DB, *paymentOrderPO) error) (*system.PaymentOrder, error) {
|
||||
var result *system.PaymentOrder
|
||||
func (r *paymentOrderRepo) withLockedOrder(ctx context.Context, provider, tradeNo string, fn func(*gorm.DB, *paymentOrderPO) error) (*bizpayment.PaymentOrder, error) {
|
||||
var result *bizpayment.PaymentOrder
|
||||
err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var po paymentOrderPO
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("provider = ? AND trade_no = ?", provider, tradeNo).First(&po).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return system.ErrPaymentOrderNotFound
|
||||
return bizpayment.ErrPaymentOrderNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -450,16 +450,16 @@ func (r *paymentOrderRepo) withLockedOrder(ctx context.Context, provider, tradeN
|
||||
return result, err
|
||||
}
|
||||
|
||||
func applyProviderIdentity(tx *gorm.DB, po *paymentOrderPO, update *system.PaymentProviderUpdate) error {
|
||||
func applyProviderIdentity(tx *gorm.DB, po *paymentOrderPO, update *bizpayment.PaymentProviderUpdate) error {
|
||||
if update.ProviderTradeNo != "" {
|
||||
if po.ProviderTradeNo != nil && *po.ProviderTradeNo != update.ProviderTradeNo {
|
||||
return system.ErrPaymentProviderConflict
|
||||
return bizpayment.ErrPaymentProviderConflict
|
||||
}
|
||||
var other paymentOrderPO
|
||||
// This check is repeated under the order transaction so a platform
|
||||
// transaction cannot be attached to a different merchant order.
|
||||
if err := tx.Where("provider = ? AND provider_trade_no = ? AND id <> ?", po.Provider, update.ProviderTradeNo, po.ID).First(&other).Error; err == nil {
|
||||
return system.ErrPaymentProviderConflict
|
||||
return bizpayment.ErrPaymentProviderConflict
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
@@ -468,7 +468,7 @@ func applyProviderIdentity(tx *gorm.DB, po *paymentOrderPO, update *system.Payme
|
||||
}
|
||||
if update.QueryID != "" {
|
||||
if po.QueryID != "" && po.QueryID != update.QueryID {
|
||||
return system.ErrPaymentProviderConflict
|
||||
return bizpayment.ErrPaymentProviderConflict
|
||||
}
|
||||
po.QueryID = update.QueryID
|
||||
}
|
||||
@@ -478,11 +478,11 @@ func applyProviderIdentity(tx *gorm.DB, po *paymentOrderPO, update *system.Payme
|
||||
func normalizeOrderPaymentStatus(value string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "success", "paid", "fulfilled":
|
||||
return system.PaymentStatusPaid
|
||||
return bizpayment.PaymentStatusPaid
|
||||
case "pending", "created", "client_pending", "processing":
|
||||
return system.PaymentStatusPending
|
||||
return bizpayment.PaymentStatusPending
|
||||
case "failed", "closed", "cancelled", "canceled":
|
||||
return system.PaymentStatusFailed
|
||||
return bizpayment.PaymentStatusFailed
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"kra/internal/biz/system"
|
||||
bizpayment "kra/internal/biz/payment"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -19,11 +19,11 @@ func newPaymentOrderRepoForTest(t *testing.T) *paymentOrderRepo {
|
||||
return &paymentOrderRepo{data: &Data{gormDB: newReloadableDB(db, nil)}}
|
||||
}
|
||||
|
||||
func testPaymentOrder() *system.PaymentOrder {
|
||||
return &system.PaymentOrder{
|
||||
TradeNo: "order-1", Provider: system.PaymentAlipay, BusinessType: "game_item", BusinessID: "item-1",
|
||||
Subject: "item", Amount: 100, Currency: "CNY", PaymentStatus: system.PaymentStatusInitialized,
|
||||
FulfillmentStatus: system.FulfillmentStatusPending, RefundStatus: system.RefundStatusNone,
|
||||
func testPaymentOrder() *bizpayment.PaymentOrder {
|
||||
return &bizpayment.PaymentOrder{
|
||||
TradeNo: "order-1", Provider: bizpayment.PaymentAlipay, BusinessType: "game_item", BusinessID: "item-1",
|
||||
Subject: "item", Amount: 100, Currency: "CNY", PaymentStatus: bizpayment.PaymentStatusInitialized,
|
||||
FulfillmentStatus: bizpayment.FulfillmentStatusPending, RefundStatus: bizpayment.RefundStatusNone,
|
||||
ConfirmationID: "11111111-1111-1111-1111-111111111111", RequestFingerprint: "fingerprint",
|
||||
}
|
||||
}
|
||||
@@ -35,36 +35,36 @@ func TestPaymentOrderRepositoryPersistsPaymentFulfillmentAndRefundState(t *testi
|
||||
if err != nil || !created {
|
||||
t.Fatalf("create order = %#v created=%v err=%v", order, created, err)
|
||||
}
|
||||
update := &system.PaymentProviderUpdate{Status: "success", ProviderStatus: "TRADE_SUCCESS", ProviderTradeNo: "provider-1", Amount: 100, Currency: "CNY", EventID: "event-1"}
|
||||
order, err = repo.ApplyPaymentResult(ctx, system.PaymentAlipay, "order-1", update)
|
||||
if err != nil || order.PaymentStatus != system.PaymentStatusPaid || order.PaidAmount != 100 {
|
||||
update := &bizpayment.PaymentProviderUpdate{Status: "success", ProviderStatus: "TRADE_SUCCESS", ProviderTradeNo: "provider-1", Amount: 100, Currency: "CNY", EventID: "event-1"}
|
||||
order, err = repo.ApplyPaymentResult(ctx, bizpayment.PaymentAlipay, "order-1", update)
|
||||
if err != nil || order.PaymentStatus != bizpayment.PaymentStatusPaid || order.PaidAmount != 100 {
|
||||
t.Fatalf("apply payment = %#v err=%v", order, err)
|
||||
}
|
||||
order, token, duplicate, err := repo.BeginPaymentFulfillment(ctx, system.PaymentAlipay, "order-1", time.Minute)
|
||||
if err != nil || duplicate || token == "" || order.FulfillmentStatus != system.FulfillmentStatusProcessing {
|
||||
order, token, duplicate, err := repo.BeginPaymentFulfillment(ctx, bizpayment.PaymentAlipay, "order-1", time.Minute)
|
||||
if err != nil || duplicate || token == "" || order.FulfillmentStatus != bizpayment.FulfillmentStatusProcessing {
|
||||
t.Fatalf("begin fulfillment = %#v token=%q duplicate=%v err=%v", order, token, duplicate, err)
|
||||
}
|
||||
if _, _, _, err = repo.BeginPaymentFulfillment(ctx, system.PaymentAlipay, "order-1", time.Minute); err == nil {
|
||||
if _, _, _, err = repo.BeginPaymentFulfillment(ctx, bizpayment.PaymentAlipay, "order-1", time.Minute); err == nil {
|
||||
t.Fatal("concurrent fulfillment was accepted")
|
||||
}
|
||||
order, err = repo.CompletePaymentFulfillment(ctx, system.PaymentAlipay, "order-1", token, true, "")
|
||||
if err != nil || order.FulfillmentStatus != system.FulfillmentStatusSucceeded {
|
||||
order, err = repo.CompletePaymentFulfillment(ctx, bizpayment.PaymentAlipay, "order-1", token, true, "")
|
||||
if err != nil || order.FulfillmentStatus != bizpayment.FulfillmentStatusSucceeded {
|
||||
t.Fatalf("complete fulfillment = %#v err=%v", order, err)
|
||||
}
|
||||
_, _, duplicate, err = repo.BeginPaymentFulfillment(ctx, system.PaymentAlipay, "order-1", time.Minute)
|
||||
_, _, duplicate, err = repo.BeginPaymentFulfillment(ctx, bizpayment.PaymentAlipay, "order-1", time.Minute)
|
||||
if err != nil || !duplicate {
|
||||
t.Fatalf("duplicate fulfillment = duplicate=%v err=%v", duplicate, err)
|
||||
}
|
||||
order, token, err = repo.BeginPaymentRefund(ctx, system.PaymentAlipay, "order-1", 40, time.Minute)
|
||||
if err != nil || token == "" || order.RefundStatus != system.RefundStatusProcessing {
|
||||
order, token, err = repo.BeginPaymentRefund(ctx, bizpayment.PaymentAlipay, "order-1", 40, time.Minute)
|
||||
if err != nil || token == "" || order.RefundStatus != bizpayment.RefundStatusProcessing {
|
||||
t.Fatalf("begin refund = %#v token=%q err=%v", order, token, err)
|
||||
}
|
||||
order, err = repo.CompletePaymentRefundRequest(ctx, system.PaymentAlipay, "order-1", token, true, "")
|
||||
if err != nil || order.RefundStatus != system.RefundStatusPending || order.RefundRequestedAmount != 40 {
|
||||
order, err = repo.CompletePaymentRefundRequest(ctx, bizpayment.PaymentAlipay, "order-1", token, true, "")
|
||||
if err != nil || order.RefundStatus != bizpayment.RefundStatusPending || order.RefundRequestedAmount != 40 {
|
||||
t.Fatalf("accept refund = %#v err=%v", order, err)
|
||||
}
|
||||
order, err = repo.ConfirmPaymentRefund(ctx, system.PaymentAlipay, "order-1", order.RefundNo, 40, true, "")
|
||||
if err != nil || order.RefundedAmount != 40 || order.PaymentStatus != system.PaymentStatusPartiallyRefunded {
|
||||
order, err = repo.ConfirmPaymentRefund(ctx, bizpayment.PaymentAlipay, "order-1", order.RefundNo, 40, true, "")
|
||||
if err != nil || order.RefundedAmount != 40 || order.PaymentStatus != bizpayment.PaymentStatusPartiallyRefunded {
|
||||
t.Fatalf("confirm refund = %#v err=%v", order, err)
|
||||
}
|
||||
}
|
||||
@@ -82,11 +82,11 @@ func TestPaymentOrderRepositoryRejectsProviderTradeReuse(t *testing.T) {
|
||||
if _, _, err := repo.CreatePaymentOrder(ctx, second); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
update := &system.PaymentProviderUpdate{Status: "success", ProviderTradeNo: "provider-1", Amount: 100, Currency: "CNY"}
|
||||
if _, err := repo.ApplyPaymentResult(ctx, system.PaymentAlipay, "order-1", update); err != nil {
|
||||
update := &bizpayment.PaymentProviderUpdate{Status: "success", ProviderTradeNo: "provider-1", Amount: 100, Currency: "CNY"}
|
||||
if _, err := repo.ApplyPaymentResult(ctx, bizpayment.PaymentAlipay, "order-1", update); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := repo.ApplyPaymentResult(ctx, system.PaymentAlipay, "order-2", update); err != system.ErrPaymentProviderConflict {
|
||||
if _, err := repo.ApplyPaymentResult(ctx, bizpayment.PaymentAlipay, "order-2", update); err != bizpayment.ErrPaymentProviderConflict {
|
||||
t.Fatalf("provider trade reuse err = %v", err)
|
||||
}
|
||||
}
|
||||
@@ -100,13 +100,13 @@ func TestPaymentOrderRepositoryListsWithFilters(t *testing.T) {
|
||||
}
|
||||
second := testPaymentOrder()
|
||||
second.TradeNo = "wechat-order-2"
|
||||
second.Provider = system.PaymentWechatV3
|
||||
second.Provider = bizpayment.PaymentWechatV3
|
||||
second.BusinessID = "item-2"
|
||||
second.ConfirmationID = "33333333-3333-3333-3333-333333333333"
|
||||
if _, _, err := repo.CreatePaymentOrder(ctx, second); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
items, total, err := repo.ListPaymentOrders(ctx, 1, 10, system.PaymentOrderFilter{Provider: system.PaymentWechatV3, TradeNo: "wechat", BusinessID: "item-2"})
|
||||
items, total, err := repo.ListPaymentOrders(ctx, 1, 10, bizpayment.PaymentOrderFilter{Provider: bizpayment.PaymentWechatV3, TradeNo: "wechat", BusinessID: "item-2"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"kra/internal/biz/system"
|
||||
integrationbiz "kra/internal/biz/integration"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -31,23 +31,23 @@ type integrationRuntimeProvider interface {
|
||||
IntegrationRuntime() *runtimeconfig.Store
|
||||
}
|
||||
|
||||
func NewIntegrationConfigRepo(data Provider) system.IntegrationConfigRepo {
|
||||
func NewIntegrationConfigRepo(data Provider) integrationbiz.IntegrationConfigRepo {
|
||||
return &integrationConfigRepo{data: data}
|
||||
}
|
||||
|
||||
func (r *integrationConfigRepo) ListIntegrationConfigs(ctx context.Context, kind string) ([]*system.IntegrationConfig, error) {
|
||||
func (r *integrationConfigRepo) ListIntegrationConfigs(ctx context.Context, kind string) ([]*integrationbiz.IntegrationConfig, error) {
|
||||
var rows []integrationConfigPO
|
||||
if err := r.data.DB().WithContext(ctx).Where("kind = ?", kind).Order("provider ASC").Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*system.IntegrationConfig, 0, len(rows))
|
||||
result := make([]*integrationbiz.IntegrationConfig, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, integrationConfigFromPO(row))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *integrationConfigRepo) FindIntegrationConfig(ctx context.Context, kind, provider string) (*system.IntegrationConfig, error) {
|
||||
func (r *integrationConfigRepo) FindIntegrationConfig(ctx context.Context, kind, provider string) (*integrationbiz.IntegrationConfig, error) {
|
||||
var row integrationConfigPO
|
||||
if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", kind, provider).First(&row).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -58,14 +58,14 @@ func (r *integrationConfigRepo) FindIntegrationConfig(ctx context.Context, kind,
|
||||
return integrationConfigFromPO(row), nil
|
||||
}
|
||||
|
||||
func (r *integrationConfigRepo) SaveIntegrationConfig(ctx context.Context, config *system.IntegrationConfig) error {
|
||||
func (r *integrationConfigRepo) SaveIntegrationConfig(ctx context.Context, config *integrationbiz.IntegrationConfig) error {
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
var row integrationConfigPO
|
||||
err := db.Where("kind = ? AND provider = ?", config.Kind, config.Provider).First(&row).Error
|
||||
values := integrationObject(config.Values)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if config.Enabled {
|
||||
if err = system.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil {
|
||||
if err = integrationbiz.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func (r *integrationConfigRepo) SaveIntegrationConfig(ctx context.Context, confi
|
||||
}
|
||||
mergeIntegrationSecrets(config.Kind, config.Provider, values, integrationObject(json.RawMessage(row.Config)))
|
||||
if config.Enabled {
|
||||
if err = system.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil {
|
||||
if err = integrationbiz.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -116,11 +116,11 @@ func integrationRuntime(provider Provider) *runtimeconfig.Store {
|
||||
return nil
|
||||
}
|
||||
|
||||
func integrationConfigFromPO(row integrationConfigPO) *system.IntegrationConfig {
|
||||
func integrationConfigFromPO(row integrationConfigPO) *integrationbiz.IntegrationConfig {
|
||||
values := integrationObject(json.RawMessage(row.Config))
|
||||
maskIntegrationSecrets(row.Kind, row.Provider, values)
|
||||
encoded, _ := json.Marshal(values)
|
||||
return &system.IntegrationConfig{Kind: row.Kind, Provider: row.Provider, Enabled: row.Enabled, Values: encoded}
|
||||
return &integrationbiz.IntegrationConfig{Kind: row.Kind, Provider: row.Provider, Enabled: row.Enabled, Values: encoded}
|
||||
}
|
||||
|
||||
func integrationObject(raw json.RawMessage) map[string]any {
|
||||
@@ -165,7 +165,7 @@ func mergeIntegrationSecrets(kind, provider string, values, old map[string]any)
|
||||
|
||||
func integrationSecretFields(kind, provider string) map[string]bool {
|
||||
result := map[string]bool{}
|
||||
if definition, ok := system.IntegrationDefinition(kind, provider); ok {
|
||||
if definition, ok := integrationbiz.IntegrationDefinition(kind, provider); ok {
|
||||
for _, field := range definition.Fields {
|
||||
if field.Secret {
|
||||
result[field.Key] = true
|
||||
|
||||
@@ -3,7 +3,7 @@ package system
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"kra/internal/biz/system"
|
||||
integrationbiz "kra/internal/biz/integration"
|
||||
"testing"
|
||||
|
||||
"kra/internal/integration/runtimeconfig"
|
||||
@@ -27,19 +27,19 @@ func TestIntegrationConfigSavePublishesUnmaskedRuntimeValues(t *testing.T) {
|
||||
provider := &integrationRuntimeTestProvider{Data: &Data{gormDB: newReloadableDB(db, nil)}, store: runtimeconfig.NewStore()}
|
||||
repo := &integrationConfigRepo{data: provider}
|
||||
|
||||
values := system.DefaultIntegrationConfig(system.IntegrationKindMQ, "rabbitmq")
|
||||
values := integrationbiz.DefaultIntegrationConfig(integrationbiz.IntegrationKindMQ, "rabbitmq")
|
||||
values["password"] = "runtime-secret"
|
||||
raw, _ := json.Marshal(values)
|
||||
if err = repo.SaveIntegrationConfig(context.Background(), &system.IntegrationConfig{Kind: system.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil {
|
||||
if err = repo.SaveIntegrationConfig(context.Background(), &integrationbiz.IntegrationConfig{Kind: integrationbiz.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
values["password"] = "******"
|
||||
raw, _ = json.Marshal(values)
|
||||
if err = repo.SaveIntegrationConfig(context.Background(), &system.IntegrationConfig{Kind: system.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil {
|
||||
if err = repo.SaveIntegrationConfig(context.Background(), &integrationbiz.IntegrationConfig{Kind: integrationbiz.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
active, ok := provider.store.Get(system.IntegrationKindMQ, "rabbitmq")
|
||||
active, ok := provider.store.Get(integrationbiz.IntegrationKindMQ, "rabbitmq")
|
||||
if !ok || !active.Enabled {
|
||||
t.Fatalf("runtime config = %#v, ok=%v", active, ok)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"kra/internal/biz/system"
|
||||
taskbiz "kra/internal/biz/task"
|
||||
"kra/pkg/database/gormkit"
|
||||
"kra/pkg/database/pagination"
|
||||
"time"
|
||||
@@ -46,14 +46,14 @@ func (taskLogPO) TableName() string { return "sys_timed_task_logs" }
|
||||
|
||||
type taskRepo struct{ data Provider }
|
||||
|
||||
func NewTaskRepo(data Provider) system.TaskRepo { return &taskRepo{data: data} }
|
||||
func taskToPO(v *system.TimedTask) taskPO {
|
||||
func NewTaskRepo(data Provider) taskbiz.TaskRepo { return &taskRepo{data: data} }
|
||||
func taskToPO(v *taskbiz.TimedTask) taskPO {
|
||||
return taskPO{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: gormkit.JSON(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: gormkit.JSON(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled}
|
||||
}
|
||||
func taskFromPO(v taskPO) *system.TimedTask {
|
||||
return &system.TimedTask{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: []byte(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: []byte(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled}
|
||||
func taskFromPO(v taskPO) *taskbiz.TimedTask {
|
||||
return &taskbiz.TimedTask{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: []byte(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: []byte(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled}
|
||||
}
|
||||
func (r *taskRepo) CreateTask(ctx context.Context, v *system.TimedTask) error {
|
||||
func (r *taskRepo) CreateTask(ctx context.Context, v *taskbiz.TimedTask) error {
|
||||
po := taskToPO(v)
|
||||
if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
@@ -70,26 +70,26 @@ func (r *taskRepo) TaskNameExists(ctx context.Context, name string, excludeID ui
|
||||
err := db.Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
func (r *taskRepo) UpdateTask(ctx context.Context, v *system.TimedTask) error {
|
||||
func (r *taskRepo) UpdateTask(ctx context.Context, v *taskbiz.TimedTask) error {
|
||||
po := taskToPO(v)
|
||||
return r.data.DB().WithContext(ctx).Model(&taskPO{}).Where("id = ?", v.ID).Select("name", "description", "spec", "with_seconds", "executor_type", "method_name", "params", "http_url", "http_method", "http_header", "http_body", "http_allow_private", "enabled").Updates(&po).Error
|
||||
}
|
||||
func (r *taskRepo) DeleteTask(ctx context.Context, id uint) error {
|
||||
return r.data.DB().WithContext(ctx).Delete(&taskPO{}, id).Error
|
||||
}
|
||||
func (r *taskRepo) FindTask(ctx context.Context, id uint) (*system.TimedTask, error) {
|
||||
func (r *taskRepo) FindTask(ctx context.Context, id uint) (*taskbiz.TimedTask, error) {
|
||||
var po taskPO
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return taskFromPO(po), nil
|
||||
}
|
||||
func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *system.TimedTask) ([]*system.TimedTask, int64, error) {
|
||||
func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *taskbiz.TimedTask) ([]*taskbiz.TimedTask, int64, error) {
|
||||
// During first-install the data layer intentionally serves a bootstrap
|
||||
// database without system tables. The scheduler starts before /init/initdb
|
||||
// and should remain idle instead of logging a missing-table SQL error.
|
||||
if !r.data.DatabaseReady() {
|
||||
return []*system.TimedTask{}, 0, nil
|
||||
return []*taskbiz.TimedTask{}, 0, nil
|
||||
}
|
||||
db := r.data.DB().WithContext(ctx).Model(&taskPO{})
|
||||
if q != nil {
|
||||
@@ -111,7 +111,7 @@ func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *system.Time
|
||||
if err := pagination.Apply(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*system.TimedTask, 0, len(pos))
|
||||
out := make([]*taskbiz.TimedTask, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, taskFromPO(po))
|
||||
}
|
||||
@@ -120,13 +120,13 @@ func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *system.Time
|
||||
func (r *taskRepo) ToggleTask(ctx context.Context, id uint, enabled bool) error {
|
||||
return r.data.DB().WithContext(ctx).Model(&taskPO{}).Where("id = ?", id).Update("enabled", enabled).Error
|
||||
}
|
||||
func (r *taskRepo) RecordTaskLog(ctx context.Context, v *system.TimedTaskLog) error {
|
||||
func (r *taskRepo) RecordTaskLog(ctx context.Context, v *taskbiz.TimedTaskLog) error {
|
||||
return r.data.DB().WithContext(ctx).Create(&taskLogPO{TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output}).Error
|
||||
}
|
||||
func taskLogFromPO(v taskLogPO) *system.TimedTaskLog {
|
||||
return &system.TimedTaskLog{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output}
|
||||
func taskLogFromPO(v taskLogPO) *taskbiz.TimedTaskLog {
|
||||
return &taskbiz.TimedTaskLog{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output}
|
||||
}
|
||||
func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint, status string) ([]*system.TimedTaskLog, int64, error) {
|
||||
func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint, status string) ([]*taskbiz.TimedTaskLog, int64, error) {
|
||||
db := r.data.DB().WithContext(ctx).Model(&taskLogPO{})
|
||||
if taskID != 0 {
|
||||
db = db.Where("task_id = ?", taskID)
|
||||
@@ -142,7 +142,7 @@ func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint
|
||||
if err := pagination.Apply(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*system.TimedTaskLog, 0, len(pos))
|
||||
out := make([]*taskbiz.TimedTaskLog, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
out = append(out, taskLogFromPO(po))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user