374 lines
16 KiB
Go
374 lines
16 KiB
Go
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_url,USDT 只返回共享收款地址。
|
||
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,
|
||
}
|
||
}
|