2026-07-09 18:15:57 +08:00

374 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package grpc
import (
"context"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
"hyapp/services/wallet-service/internal/domain/ledger"
)
// ListThirdPartyPaymentChannels 返回三方支付渠道、国家和方式树,后台用它直接渲染二级展开列表。
func (s *Server) ListThirdPartyPaymentChannels(ctx context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
channels, err := s.svc.ListThirdPartyPaymentChannels(ctx, ledger.ListThirdPartyPaymentChannelsQuery{
AppCode: req.GetAppCode(),
ProviderCode: req.GetProviderCode(),
Status: req.GetStatus(),
IncludeDisabledMethods: req.GetIncludeDisabledMethods(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &walletv1.ListThirdPartyPaymentChannelsResponse{Channels: make([]*walletv1.ThirdPartyPaymentChannel, 0, len(channels))}
for _, channel := range channels {
resp.Channels = append(resp.Channels, thirdPartyPaymentChannelToProto(channel))
}
return resp, nil
}
// SetThirdPartyPaymentMethodStatus 开关某个国家下的具体支付方式;渠道本身不被连带关闭。
func (s *Server) SetThirdPartyPaymentMethodStatus(ctx context.Context, req *walletv1.SetThirdPartyPaymentMethodStatusRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
method, err := s.svc.SetThirdPartyPaymentMethodStatus(ctx, ledger.ThirdPartyPaymentMethodStatusCommand{
AppCode: req.GetAppCode(),
MethodID: req.GetMethodId(),
Enabled: req.GetEnabled(),
OperatorUserID: req.GetOperatorUserId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.ThirdPartyPaymentMethodResponse{Method: thirdPartyPaymentMethodToProto(method)}, nil
}
// UpdateThirdPartyPaymentRate 保存 USD 到国家本币汇率H5 只展示已配置正汇率的国家方式。
func (s *Server) UpdateThirdPartyPaymentRate(ctx context.Context, req *walletv1.UpdateThirdPartyPaymentRateRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
method, err := s.svc.UpdateThirdPartyPaymentRate(ctx, ledger.ThirdPartyPaymentRateCommand{
AppCode: req.GetAppCode(),
MethodID: req.GetMethodId(),
USDToCurrencyRate: req.GetUsdToCurrencyRate(),
OperatorUserID: req.GetOperatorUserId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.ThirdPartyPaymentMethodResponse{Method: thirdPartyPaymentMethodToProto(method)}, nil
}
// SyncThirdPartyPaymentMethods 从支付商同步当前商户实际开通的支付方式;写库仍由 wallet-service 统一负责。
func (s *Server) SyncThirdPartyPaymentMethods(ctx context.Context, req *walletv1.SyncThirdPartyPaymentMethodsRequest) (*walletv1.SyncThirdPartyPaymentMethodsResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
result, err := s.svc.SyncThirdPartyPaymentMethods(ctx, ledger.ThirdPartyPaymentMethodSyncCommand{
AppCode: req.GetAppCode(),
ProviderCode: req.GetProviderCode(),
OperatorUserID: req.GetOperatorUserId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &walletv1.SyncThirdPartyPaymentMethodsResponse{
ProviderCode: result.ProviderCode,
ScannedCountryCount: int32(result.ScannedCountryCount),
FetchedCountryCount: int32(result.FetchedCountryCount),
CreatedCount: int32(result.CreatedCount),
UpdatedCount: int32(result.UpdatedCount),
SkippedCount: int32(result.SkippedCount),
Issues: make([]*walletv1.ThirdPartyPaymentMethodSyncIssue, 0, len(result.Issues)),
}
for _, issue := range result.Issues {
resp.Issues = append(resp.Issues, thirdPartyPaymentMethodSyncIssueToProto(issue))
}
return resp, nil
}
// ListH5RechargeOptions 返回 H5 当前账号的商品和支付方式,普通用户和币商由 audience_type 分流。
func (s *Server) ListH5RechargeOptions(ctx context.Context, req *walletv1.H5RechargeOptionsRequest) (*walletv1.H5RechargeOptionsResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
options, err := s.svc.ListH5RechargeOptions(ctx, ledger.H5RechargeOptionsQuery{
AppCode: req.GetAppCode(),
TargetUserID: req.GetTargetUserId(),
TargetRegionID: req.GetTargetRegionId(),
TargetCountryCode: req.GetTargetCountryCode(),
AudienceType: req.GetAudienceType(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &walletv1.H5RechargeOptionsResponse{
Products: make([]*walletv1.RechargeProduct, 0, len(options.Products)),
PaymentMethods: make([]*walletv1.ThirdPartyPaymentMethod, 0, len(options.PaymentMethods)),
UsdtTrc20Enabled: options.USDTTRC20Enabled,
UsdtTrc20Address: options.USDTTRC20Address,
}
for _, product := range options.Products {
resp.Products = append(resp.Products, rechargeProductToProto(product))
}
for _, method := range options.PaymentMethods {
resp.PaymentMethods = append(resp.PaymentMethods, thirdPartyPaymentMethodToProto(method))
}
return resp, nil
}
// CreateH5RechargeOrder 创建外部充值订单MiFaPay 只返回 pay_urlUSDT 只返回共享收款地址。
func (s *Server) CreateH5RechargeOrder(ctx context.Context, req *walletv1.CreateH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
order, err := s.svc.CreateH5RechargeOrder(ctx, ledger.CreateExternalRechargeOrderCommand{
AppCode: req.GetAppCode(),
CommandID: req.GetCommandId(),
TargetUserID: req.GetTargetUserId(),
TargetRegionID: req.GetTargetRegionId(),
TargetCountryID: req.GetTargetCountryId(),
TargetCountryCode: req.GetTargetCountryCode(),
AudienceType: req.GetAudienceType(),
ProductID: req.GetProductId(),
ProviderCode: req.GetProviderCode(),
PaymentMethodID: req.GetPaymentMethodId(),
ReturnURL: req.GetReturnUrl(),
NotifyURL: req.GetNotifyUrl(),
ClientIP: req.GetClientIp(),
Language: req.GetLanguage(),
PayerName: req.GetPayerName(),
PayerAccount: req.GetPayerAccount(),
PayerEmail: req.GetPayerEmail(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.H5RechargeOrderResponse{Order: externalRechargeOrderToProto(order)}, nil
}
// CreateTemporaryRechargeOrder 创建运营临时支付链接;支付成功只标记 paid不触发钱包入账。
func (s *Server) CreateTemporaryRechargeOrder(ctx context.Context, req *walletv1.CreateTemporaryRechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
order, err := s.svc.CreateTemporaryRechargeOrder(ctx, ledger.CreateTemporaryExternalRechargeOrderCommand{
AppCode: req.GetAppCode(),
CommandID: req.GetCommandId(),
CoinAmount: req.GetCoinAmount(),
USDMinorAmount: req.GetUsdMinorAmount(),
ProviderCode: req.GetProviderCode(),
PaymentMethodID: req.GetPaymentMethodId(),
ReturnURL: req.GetReturnUrl(),
NotifyURL: req.GetNotifyUrl(),
ClientIP: req.GetClientIp(),
Language: req.GetLanguage(),
PayerName: req.GetPayerName(),
PayerAccount: req.GetPayerAccount(),
PayerEmail: req.GetPayerEmail(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.H5RechargeOrderResponse{Order: externalRechargeOrderToProto(order)}, nil
}
// GetTemporaryRechargeOrder 给运营页验证临时链接是否已支付,并顺带触发三方查单刷新状态。
func (s *Server) GetTemporaryRechargeOrder(ctx context.Context, req *walletv1.GetTemporaryRechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
order, err := s.svc.GetTemporaryRechargeOrder(ctx, req.GetAppCode(), req.GetOrderId())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.H5RechargeOrderResponse{Order: externalRechargeOrderToProto(order)}, nil
}
// ListTemporaryRechargeOrders 返回后台临时支付链接列表;这里只读订单快照,不批量访问上游支付商。
func (s *Server) ListTemporaryRechargeOrders(ctx context.Context, req *walletv1.ListTemporaryRechargeOrdersRequest) (*walletv1.ListTemporaryRechargeOrdersResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
orders, total, err := s.svc.ListTemporaryRechargeOrders(ctx, ledger.ListTemporaryExternalRechargeOrdersQuery{
AppCode: req.GetAppCode(),
Status: req.GetStatus(),
ProviderCode: req.GetProviderCode(),
Keyword: req.GetKeyword(),
Page: req.GetPage(),
PageSize: req.GetPageSize(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &walletv1.ListTemporaryRechargeOrdersResponse{Orders: make([]*walletv1.ExternalRechargeOrder, 0, len(orders)), Total: total}
for _, order := range orders {
resp.Orders = append(resp.Orders, externalRechargeOrderToProto(order))
}
return resp, nil
}
// SubmitH5RechargeTx 接收 USDT tx_hash链上校验和幂等入账仍在 wallet-service 事务里完成。
func (s *Server) SubmitH5RechargeTx(ctx context.Context, req *walletv1.SubmitH5RechargeTxRequest) (*walletv1.H5RechargeOrderResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
order, err := s.svc.SubmitH5RechargeTx(ctx, ledger.SubmitExternalRechargeTxCommand{
AppCode: req.GetAppCode(),
OrderID: req.GetOrderId(),
TargetUserID: req.GetTargetUserId(),
TxHash: req.GetTxHash(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.H5RechargeOrderResponse{Order: externalRechargeOrderToProto(order)}, nil
}
// GetH5RechargeOrder 给 H5 轮询订单状态target_user_id 用于避免无 token 场景越权看单。
func (s *Server) GetH5RechargeOrder(ctx context.Context, req *walletv1.GetH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
order, err := s.svc.GetH5RechargeOrder(ctx, req.GetAppCode(), req.GetOrderId(), req.GetTargetUserId())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.H5RechargeOrderResponse{Order: externalRechargeOrderToProto(order)}, nil
}
// VerifyCoinSellerRechargeReceipt 只读校验币商充值凭证,不创建外部订单,也不触发钱包入账。
func (s *Server) VerifyCoinSellerRechargeReceipt(ctx context.Context, req *walletv1.VerifyCoinSellerRechargeReceiptRequest) (*walletv1.VerifyCoinSellerRechargeReceiptResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
receipt, err := s.svc.VerifyCoinSellerRechargeReceipt(ctx, ledger.VerifyCoinSellerRechargeReceiptCommand{
RequestID: req.GetRequestId(),
AppCode: req.GetAppCode(),
ProviderCode: req.GetProviderCode(),
ExternalOrderNo: req.GetExternalOrderNo(),
Chain: req.GetChain(),
USDMinorAmount: req.GetUsdMinorAmount(),
OrderDateMS: req.GetOrderDateMs(),
ProviderCountryCode: req.GetProviderCountryCode(),
ProviderCurrencyCode: req.GetProviderCurrencyCode(),
ProviderAmountMinor: req.GetProviderAmountMinor(),
ProviderPayType: req.GetProviderPayType(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return coinSellerRechargeReceiptVerificationToProto(receipt), nil
}
// HandleMifapayNotify 验签并处理 MiFaPay 回调;成功或重复成功由 HTTP 层按 response_text 返回大写 SUCCESS。
func (s *Server) HandleMifapayNotify(ctx context.Context, req *walletv1.HandleMifapayNotifyRequest) (*walletv1.HandleMifapayNotifyResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
order, accepted, err := s.svc.HandleMifapayNotify(ctx, req.GetAppCode(), ledger.MifaPayNotification{
MerAccount: req.GetMerAccount(),
Data: req.GetData(),
Sign: req.GetSign(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
responseText := "FAIL"
if accepted {
responseText = "SUCCESS"
}
return &walletv1.HandleMifapayNotifyResponse{Accepted: accepted, ResponseText: responseText, OrderId: order.OrderID}, nil
}
// HandleV5PayNotify 验签并处理 V5Pay 回调;成功或重复成功由 HTTP 层按 response_text 返回小写 success。
func (s *Server) HandleV5PayNotify(ctx context.Context, req *walletv1.HandleV5PayNotifyRequest) (*walletv1.HandleV5PayNotifyResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
order, accepted, err := s.svc.HandleV5PayNotify(ctx, req.GetAppCode(), ledger.V5PayNotification{
Fields: req.GetFields(),
RawJSON: req.GetRawJson(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
responseText := "fail"
if accepted {
responseText = "success"
}
return &walletv1.HandleV5PayNotifyResponse{Accepted: accepted, ResponseText: responseText, OrderId: order.OrderID}, nil
}
func coinSellerRechargeReceiptVerificationToProto(receipt ledger.CoinSellerRechargeReceiptVerification) *walletv1.VerifyCoinSellerRechargeReceiptResponse {
return &walletv1.VerifyCoinSellerRechargeReceiptResponse{
Verified: receipt.Verified,
ProviderCode: receipt.ProviderCode,
ExternalOrderNo: receipt.ExternalOrderNo,
ProviderOrderId: receipt.ProviderOrderID,
Status: receipt.Status,
CurrencyCode: receipt.CurrencyCode,
ProviderAmountMinor: receipt.ProviderAmountMinor,
RawJson: receipt.RawJSON,
Chain: receipt.Chain,
ReceiveAddress: receipt.ReceiveAddress,
VerifiedAtMs: receipt.VerifiedAtMS,
FailureReason: receipt.FailureReason,
}
}
func thirdPartyPaymentChannelToProto(channel ledger.ThirdPartyPaymentChannel) *walletv1.ThirdPartyPaymentChannel {
resp := &walletv1.ThirdPartyPaymentChannel{
AppCode: channel.AppCode,
ProviderCode: channel.ProviderCode,
ProviderName: channel.ProviderName,
Status: channel.Status,
SortOrder: channel.SortOrder,
Methods: make([]*walletv1.ThirdPartyPaymentMethod, 0, len(channel.Methods)),
}
for _, method := range channel.Methods {
resp.Methods = append(resp.Methods, thirdPartyPaymentMethodToProto(method))
}
return resp
}
func thirdPartyPaymentMethodSyncIssueToProto(issue ledger.ThirdPartyPaymentMethodSyncIssue) *walletv1.ThirdPartyPaymentMethodSyncIssue {
return &walletv1.ThirdPartyPaymentMethodSyncIssue{
CountryCode: issue.CountryCode,
CurrencyCode: issue.CurrencyCode,
Code: issue.Code,
Message: issue.Message,
}
}
func thirdPartyPaymentMethodToProto(method ledger.ThirdPartyPaymentMethod) *walletv1.ThirdPartyPaymentMethod {
return &walletv1.ThirdPartyPaymentMethod{
MethodId: method.MethodID,
AppCode: method.AppCode,
ProviderCode: method.ProviderCode,
ProviderName: method.ProviderName,
CountryCode: method.CountryCode,
CountryName: method.CountryName,
CurrencyCode: method.CurrencyCode,
PayWay: method.PayWay,
PayType: method.PayType,
MethodName: method.MethodName,
LogoUrl: method.LogoURL,
Status: method.Status,
UsdToCurrencyRate: method.USDToCurrencyRate,
SortOrder: method.SortOrder,
CreatedAtMs: method.CreatedAtMS,
UpdatedAtMs: method.UpdatedAtMS,
}
}
func externalRechargeOrderToProto(order ledger.ExternalRechargeOrder) *walletv1.ExternalRechargeOrder {
return &walletv1.ExternalRechargeOrder{
OrderId: order.OrderID,
AppCode: order.AppCode,
TargetUserId: order.TargetUserID,
TargetRegionId: order.TargetRegionID,
TargetCountryId: order.TargetCountryID,
TargetCountryCode: order.TargetCountryCode,
AudienceType: order.AudienceType,
ProductId: order.ProductID,
ProductName: order.ProductName,
CoinAmount: order.CoinAmount,
UsdMinorAmount: order.USDMinorAmount,
ProviderCode: order.ProviderCode,
PaymentMethodId: order.PaymentMethodID,
CountryCode: order.CountryCode,
CurrencyCode: order.CurrencyCode,
ProviderAmountMinor: order.ProviderAmountMinor,
PayWay: order.PayWay,
PayType: order.PayType,
PayUrl: order.PayURL,
ProviderOrderId: order.ProviderOrderID,
TxHash: order.TxHash,
ReceiveAddress: order.ReceiveAddress,
Status: order.Status,
FailureReason: order.FailureReason,
TransactionId: order.TransactionID,
CreatedAtMs: order.CreatedAtMS,
UpdatedAtMs: order.UpdatedAtMS,
IdempotentReplay: order.IdempotentReplay,
}
}