10个文件已修改
7个文件已添加
1085 ■■■■■ 已修改文件
ApiTools.Application/ApiTools.Application.xml 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Application/ChannelWallets/Commands/ChannelWalletCommandHandler.cs 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Application/ChannelWallets/Queries/ChannelWalletQueryHandler.cs 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Application/WxUtils/Commands/SaveWxCommandHandler.cs 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Application/WxUtils/Commands/SaveWxSettingCommandHandler.cs 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Application/WxUtils/Queries/GetWxSettingQueryHandler.cs 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Core/ApiTools.Core.csproj 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Core/ApiTools.Core.xml 337 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Core/Entities/WxmpUtils/WxSetting.cs 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Core/Enums/Resources/EnumResourceController.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Core/Jobs/RefreshChannelWalletTransactionStatusJob.cs 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Core/Models/ChannelWallets/Queries/GetChannelWalletTransactionsQuery.cs 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Core/Models/WxmpUtils/Commands/SaveWxSendCommand.cs 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Core/Models/WxmpUtils/Commands/SaveWxSettingCommand.cs 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Core/Utils/DbUtils/DbUtils.cs 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Core/Utils/WxmpUtils/WxmpGetQrCodeRequest.cs 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Core/Utils/WxmpUtils/WxmpUtils.cs 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ApiTools.Application/ApiTools.Application.xml
@@ -191,6 +191,14 @@
            <param name="cancellationToken"></param>
            <returns></returns>
        </member>
        <member name="M:ApiTools.Application.ChannelWalletQueryHandler.Handle(ApiTools.Core.GetChannelWalletTransactionsQuery,System.Threading.CancellationToken)">
            <summary>
            查询渠道钱包交易详情
            </summary>
            <param name="request"></param>
            <param name="cancellationToken"></param>
            <returns></returns>
        </member>
        <member name="M:ApiTools.Application.SendSmsCommandHandler.Handle(ApiTools.Core.SendSmsCommand,System.Threading.CancellationToken)">
            <summary>
            发送短信
@@ -279,5 +287,21 @@
            <param name="cancellationToken"></param>
            <returns></returns>
        </member>
        <member name="M:ApiTools.CommonServer.Application.SaveWxCommandHandler.Handle(ApiTools.Core.SaveWxSendCommand,System.Threading.CancellationToken)">
            <summary>
            该接口用于发送订阅消息
            </summary>
            <param name="request"></param>
            <param name="cancellationToken"></param>
            <returns></returns>
        </member>
        <member name="M:ApiTools.CommonServer.Application.SaveWxSettingCommandHandler.Handle(ApiTools.Core.SaveWxSettingCommand,System.Threading.CancellationToken)">
            <summary>
            保存微信配置
            </summary>
            <param name="request"></param>
            <param name="cancellationToken"></param>
            <returns></returns>
        </member>
    </members>
</doc>
ApiTools.Application/ChannelWallets/Commands/ChannelWalletCommandHandler.cs
@@ -90,37 +90,76 @@
            await channelWalletService.GetEnterpriseWalletBalance(wallet);
            if (request.Amount > wallet.Balance) throw Oops.Oh(EnumErrorCodeType.s404, "余额不足");
            var checkExist = await channelWalletTransactionRepository.GetQueryable()
                .AnyAsync(it => it.WalletId == wallet.Id && it.OutCode == request.OutCode);
            if (checkExist) throw Oops.Oh(EnumErrorCodeType.s405, "交易单号");
            var transaction = new ChannelWalletTransaction();
            transaction.Type = EnumWalletTransactionType.Transfer;
            transaction.WalletId = wallet.Id;
            transaction.OutCode = request.OutCode;
            transaction.ConcurrencyLock = $"{logier.ChannelId}:{transaction.OutCode}";
            transaction.Amount = request.Amount;
            transaction.Remark = request.Remark;
            transaction.TransactionStatus = EnumWalletTransactionStatus.WaitSubmit;
            transaction.Balance = wallet.Balance;
            transaction.AfterBalance = wallet.Balance - transaction.Amount;
            transaction.OutOperatorId = request.OutOperatorId;
            transaction.OperatorTime = request.OperatorTime;
            transaction.PayerName = wallet.Name;
            transaction.PayerAccount = wallet.Identity;
            transaction.PayerBank = wallet.Bank;
            transaction.PayerBankBranch = wallet.BankBranch;
            transaction.OutReceiveId = request.OutReceiveId;
            transaction.ReceiveBank = request.ReceiveBank;
            transaction.ReceiveBankBranch = request.ReceiveBankBranch;
            transaction.ReceiveName = request.ReceiveName;
            transaction.ReceiveIdentity = request.ReceiveIdentity;
            transaction.ReceiveAccount = request.ReceiveAccount;
            transaction.Currency = request.Currency;
            transaction.Purpose = request.Purpose;
            transaction.Remark = request.Remark;
            await channelWalletTransactionRepository.SetCode(transaction);
            await channelWalletTransactionRepository.InsertNowAsync(transaction);
            var transaction = await channelWalletTransactionRepository.GetQueryable()
                .Where(it => it.WalletId == wallet.Id && it.OutCode == request.OutCode)
                .FirstOrDefaultAsync();
            if (transaction == null)
            {
                transaction = new ChannelWalletTransaction();
                transaction.Type = EnumWalletTransactionType.Transfer;
                transaction.WalletId = wallet.Id;
                transaction.OutCode = request.OutCode;
                transaction.ConcurrencyLock = $"{logier.ChannelId}:{transaction.OutCode}";
                transaction.TransactionStatus = EnumWalletTransactionStatus.WaitSubmit;
                transaction.Amount = request.Amount;
                transaction.Balance = wallet.Balance;
                transaction.AfterBalance = wallet.Balance - transaction.Amount;
                transaction.OutOperatorId = request.OutOperatorId;
                transaction.OperatorTime = request.OperatorTime;
                transaction.PayerName = wallet.Name;
                transaction.PayerAccount = wallet.Identity;
                transaction.PayerBank = wallet.Bank;
                transaction.PayerBankBranch = wallet.BankBranch;
                transaction.OutReceiveId = request.OutReceiveId;
                transaction.ReceiveBank = request.ReceiveBank;
                transaction.ReceiveBankBranch = request.ReceiveBankBranch;
                transaction.ReceiveName = request.ReceiveName;
                transaction.ReceiveIdentity = request.ReceiveIdentity;
                transaction.ReceiveAccount = request.ReceiveAccount;
                transaction.Currency = request.Currency;
                transaction.Purpose = request.Purpose;
                transaction.Remark = request.Remark;
                await channelWalletTransactionRepository.SetCode(transaction);
                await channelWalletTransactionRepository.InsertNowAsync(transaction);
            }
            else
            {
                switch (transaction.TransactionStatus)
                {
                    case EnumWalletTransactionStatus.WaitSubmit:
                        throw Oops.Oh(EnumErrorCodeType.s510, "已存在正在处理的转账,请勿重复操作");
                    case EnumWalletTransactionStatus.WaitPay:
                    case EnumWalletTransactionStatus.Dealing:
                        throw Oops.Oh(EnumErrorCodeType.s510, "转账正在进行中,请勿重复操作");
                    case EnumWalletTransactionStatus.Success:
                        throw Oops.Oh(EnumErrorCodeType.s510, "已转账,请勿重复操作");
                    case EnumWalletTransactionStatus.Refund:
                    case EnumWalletTransactionStatus.Fail:
                        transaction.TransactionStatus = EnumWalletTransactionStatus.WaitSubmit;
                        transaction.Amount = request.Amount;
                        transaction.Balance = wallet.Balance;
                        transaction.AfterBalance = wallet.Balance - transaction.Amount;
                        transaction.OutOperatorId = request.OutOperatorId;
                        transaction.OperatorTime = request.OperatorTime;
                        transaction.PayerName = wallet.Name;
                        transaction.PayerAccount = wallet.Identity;
                        transaction.PayerBank = wallet.Bank;
                        transaction.PayerBankBranch = wallet.BankBranch;
                        transaction.OutReceiveId = request.OutReceiveId;
                        transaction.ReceiveBank = request.ReceiveBank;
                        transaction.ReceiveBankBranch = request.ReceiveBankBranch;
                        transaction.ReceiveName = request.ReceiveName;
                        transaction.ReceiveIdentity = request.ReceiveIdentity;
                        transaction.ReceiveAccount = request.ReceiveAccount;
                        transaction.Currency = request.Currency;
                        transaction.Purpose = request.Purpose;
                        transaction.Remark = request.Remark;
                        await channelWalletTransactionRepository.UpdateNowAsync(transaction);
                        break;
                    default:
                        break;
                }
            }
            await channelWalletService.Transfer(wallet, transaction);
            return new SubmitChannelWalletTransferCommandResult
            {
ApiTools.Application/ChannelWallets/Queries/ChannelWalletQueryHandler.cs
@@ -18,7 +18,8 @@
            ChannelWalletTransactionRepository channelWalletTransactionRepository
        ) :
        IRequestHandler<GetChannelPingAnPayWalletQuery, GetChannelPingAnPayWalletQueryResult>,
        IRequestHandler<GetChannelWalletTransactionQuery, GetChannelWalletTransactionQueryResult>
        IRequestHandler<GetChannelWalletTransactionQuery, GetChannelWalletTransactionQueryResult>,
        IRequestHandler<GetChannelWalletTransactionsQuery, GetChannelWalletTransactionsQueryResult>
    {
        private readonly ChannelWalletRepository channelWalletRepository = channelWalletRepository;
        private readonly ChannelWalletTransactionRepository channelWalletTransactionRepository = channelWalletTransactionRepository;
@@ -51,5 +52,28 @@
                .FirstOrDefaultAsync();
        }
        /// <summary>
        /// 查询渠道钱包交易详情
        /// </summary>
        /// <param name="request"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<GetChannelWalletTransactionsQueryResult> Handle(GetChannelWalletTransactionsQuery request, CancellationToken cancellationToken)
        {
            var list = new GetChannelWalletTransactionsQueryResult();
            list.Items = await channelWalletTransactionRepository.GetQueryable()
                .Where(it => request.OutCodes.Contains(it.OutCode))
                .ProjectToType<GetChannelWalletTransactionsQueryResultItem>()
                .ToListAsync();
            foreach (var item in list.Items)
            {
                if (item.EreceiptStatus == EnumWalletTransactionEreceiptStatus.SUCCESS
                    && item.EreceiptDownloadOssUrl.IsNotNull())
                {
                    item.EreceiptDownloadOssFullUrl = AliyunOSSUtils.GetUrl(item.EreceiptDownloadOssUrl);
                }
            }
            return list;
        }
    }
}
ApiTools.Application/WxUtils/Commands/SaveWxCommandHandler.cs
New file
@@ -0,0 +1,36 @@
using ApiTools.Core;
using Baidu.Aip;
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ApiTools.CommonServer.Application
{
    public class SaveWxCommandHandler(WxmpUtils utils) : IRequestHandler<SaveWxSendCommand, string>
    {
        private readonly WxmpUtils utils = utils;
        /// <summary>
        /// 该接口用于发送订阅消息
        /// </summary>
        /// <param name="request"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<string> Handle(SaveWxSendCommand request, CancellationToken cancellationToken)
        {
            return await utils.WxSend(new SendRequest
            {
                WxmpCode = request.WxmpCode,
                EnvVersion = request.EnvVersion,
                template_id = request.template_id,
                page = request.page,
                touser = request.touser,
                data = request.data,
                miniprogram_state = request.miniprogram_state,
                lang = request.lang
            });
        }
    }
}
ApiTools.Application/WxUtils/Commands/SaveWxSettingCommandHandler.cs
New file
@@ -0,0 +1,53 @@
using ApiTools.Core;
using Furion.DatabaseAccessor;
using Mapster;
using MediatR;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ApiTools.CommonServer.Application
{
    public class SaveWxSettingCommandHandler(IRepository<WxSetting> rep) :IRequestHandler<SaveWxSettingCommand, Guid>
    {
        private readonly IRepository<WxSetting> rep = rep;
        /// <summary>
        /// 保存微信配置
        /// </summary>
        /// <param name="request"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<Guid> Handle(SaveWxSettingCommand request, CancellationToken cancellationToken)
        {
            var logier = JwtUtils.GetCurrentLogier();
            var entity = await rep.AsQueryable()
                .Where(it => it.Code == request.Code)
                .FirstOrDefaultAsync();
            var add = false;
            if (entity == null)
            {
                entity = new WxSetting();
                entity.Code = request.Code;
                entity.AppId = request.AppId;
                entity.AppSecret = request.AppSecret;
                entity.EnvVersion = request.EnvVersion;
                add = true;
            }
            request.Adapt(entity);
            if (add)
            {
                await rep.InsertAsync(entity);
            }
            else
            {
                await rep.UpdateAsync(entity);
            }
            return entity.Id;
            //return Guid.NewGuid();
        }
    }
}
ApiTools.Application/WxUtils/Queries/GetWxSettingQueryHandler.cs
New file
@@ -0,0 +1,34 @@
using ApiTools.Core;
using Furion.DatabaseAccessor;
using MediatR;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ApiTools.CommonServer.Application
{
    //public class GetWxSettingQueryHandler(IRepository<SmsSetting> rep, IRepository<SmsLog> repSmsLog) : IRequestHandler<GetSmsSettingQuery, GetSmsSettingQueryResult>
    //{
    //    private readonly IRepository<SmsSetting> rep = rep;
    //    private readonly IRepository<SmsLog> repSmsLog = repSmsLog;
    //    /// <summary>
    //    /// 查询短信配置
    //    /// </summary>
    //    /// <param name="request"></param>
    //    /// <param name="cancellationToken"></param>
    //    /// <returns></returns>
    //    public async Task<GetSmsSettingQueryResult> Handle(GetSmsSettingQuery request, CancellationToken cancellationToken)
    //    {
    //        var logier = JwtUtils.GetCurrentLogier();
    //        var detail = await rep.AsQueryable().AsNoTracking()
    //            .Where(it => it.ChannelId == logier.ChannelId)
    //            .GetDetail<SmsSetting, GetSmsSettingQueryResult>();
    //        detail.Accesses = detail.Accesses.OrderBy(it => it.Sort).ToList();
    //        return detail;
    //    }
    //}
}
ApiTools.Core/ApiTools.Core.csproj
@@ -39,4 +39,8 @@
      </None>
    </ItemGroup>
    <ItemGroup>
      <Folder Include="Models\WxmpUtils\Queries\" />
    </ItemGroup>
</Project>
ApiTools.Core/ApiTools.Core.xml
@@ -1280,6 +1280,31 @@
            状态
            </summary>
        </member>
        <member name="T:ApiTools.Core.WxSetting">
            <summary>
            微信配置
            </summary>
        </member>
        <member name="P:ApiTools.Core.WxSetting.Code">
            <summary>
            编号
            </summary>
        </member>
        <member name="P:ApiTools.Core.WxSetting.AppId">
            <summary>
            小程序ID
            </summary>
        </member>
        <member name="P:ApiTools.Core.WxSetting.AppSecret">
            <summary>
            小程序密钥
            </summary>
        </member>
        <member name="P:ApiTools.Core.WxSetting.EnvVersion">
            <summary>
            版本
            </summary>
        </member>
        <member name="F:ApiTools.Core.EnumErrorCodeType.s400">
            <summary>
            参数错误
@@ -1498,6 +1523,11 @@
        <member name="F:ApiTools.Core.EnumResourceController.CommonServerSmsUtils">
            <summary>
            短信工具
            </summary>
        </member>
        <member name="F:ApiTools.Core.EnumResourceController.CommonServerWxUtils">
            <summary>
            微信工具
            </summary>
        </member>
        <member name="F:ApiTools.Core.EnumResourceController.UserServerAuth">
@@ -2418,6 +2448,11 @@
            余额
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelPingAnPayWalletQueryResult.SignStatus">
            <summary>
            签约状态
            </summary>
        </member>
        <member name="T:ApiTools.Core.GetChannelWalletTransactionQuery">
            <summary>
            查询渠道钱包交易详情
@@ -2594,6 +2629,201 @@
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionQueryResult.TransactionStatus">
            <summary>
            状态
            </summary>
        </member>
        <member name="T:ApiTools.Core.GetChannelWalletTransactionsQuery">
            <summary>
            查询渠道钱包交易详情
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQuery.OutCodes">
            <summary>
            外部订单号
            </summary>
        </member>
        <member name="T:ApiTools.Core.GetChannelWalletTransactionsQueryResult">
            <summary>
            查询渠道钱包交易详情
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResult.Items">
            <summary>
            项
            </summary>
        </member>
        <member name="T:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem">
            <summary>
            查询渠道钱包交易详情
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.Id">
            <summary>
            交易Id
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.Type">
            <summary>
            类型
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.Code">
            <summary>
            订单号
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.OutCode">
            <summary>
            外部订单号
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.ConcurrencyLock">
            <summary>
            并发锁
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.Amount">
            <summary>
            金额
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.Balance">
            <summary>
            余额
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.AfterBalance">
            <summary>
            收支后余额
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.OutOperatorId">
            <summary>
            外部操作人Id
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.OperatorTime">
            <summary>
            操作时间
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.PayerAccount">
            <summary>
            付款人账户
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.PayerName">
            <summary>
            付款人名称
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.PayerBank">
            <summary>
            付款人开户行
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.PayerBankBranch">
            <summary>
            付款人支行
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.OutReceiveId">
            <summary>
            外部收款人Id
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.ReceiveName">
            <summary>
            收款人姓名
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.ReceiveIdentity">
            <summary>
            收款人身份证号
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.ReceiveAccount">
            <summary>
            收款账户
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.ReceiveBank">
            <summary>
            收款人开户行
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.ReceiveBankBranch">
            <summary>
            收款人支行
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.Currency">
            <summary>
            币种
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.Purpose">
            <summary>
            用途
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.Remark">
            <summary>
            备注
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.ErrorCode">
            <summary>
            查询到的订单状态为FAIL失败或REFUND退票时,返回错误代码
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.FailReason">
            <summary>
            查询到的订单状态为FAIL失败或REFUND退票时,返回具体的原因。
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.TransDate">
            <summary>
            订单支付时间
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.OrderFee">
            <summary>
            预计收费金额(元),转账到银行卡专用
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.EreceiptFileId">
            <summary>
            电子收据Id
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.EreceiptDownloadUrl">
            <summary>
            电子收据下载链接
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.EreceiptDownloadOssUrl">
            <summary>
            电子收据下载链接
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.EreceiptDownloadOssFullUrl">
            <summary>
            电子收据下载链接
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.EreceiptStatus">
            <summary>
            电子收据状态
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.EreceiptErrorMessage">
            <summary>
            电子收据错误信息
            </summary>
        </member>
        <member name="P:ApiTools.Core.GetChannelWalletTransactionsQueryResultItem.TransactionStatus">
            <summary>
            状态
            </summary>
@@ -3036,6 +3266,66 @@
        <member name="P:ApiTools.Core.GetSmsSettingQueryResultAccess.SignName">
            <summary>
            签名名称
            </summary>
        </member>
        <member name="T:ApiTools.Core.SaveWxSendCommand">
            <summary>
            该接口用于发送订阅消息
            </summary>
        </member>
        <member name="P:ApiTools.Core.SaveWxSendCommand.WxmpCode">
            <summary>
            小程序编号
            </summary>
        </member>
        <member name="P:ApiTools.Core.SaveWxSendCommand.EnvVersion">
            <summary>
            要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"。默认是正式版。
            </summary>
        </member>
        <member name="P:ApiTools.Core.SaveWxSendCommand.template_id">
            <summary>
            所需下发的订阅模板id
            </summary>
        </member>
        <member name="P:ApiTools.Core.SaveWxSendCommand.page">
            <summary>
            点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转
            </summary>
        </member>
        <member name="P:ApiTools.Core.SaveWxSendCommand.touser">
            <summary>
            接收者(用户)的 openid
            </summary>
        </member>
        <member name="P:ApiTools.Core.SaveWxSendCommand.data">
            <summary>
            模板内容,格式形如{ "phrase3": { "value": "审核通过" }, "name1": { "value": "订阅" }, "date2": { "value": "2019-12-25 09:42" } }
            </summary>
        </member>
        <member name="T:ApiTools.Core.SaveWxSettingCommand">
            <summary>
            保存微信配置
            </summary>
        </member>
        <member name="P:ApiTools.Core.SaveWxSettingCommand.Code">
            <summary>
            编号
            </summary>
        </member>
        <member name="P:ApiTools.Core.SaveWxSettingCommand.AppId">
            <summary>
            小程序ID
            </summary>
        </member>
        <member name="P:ApiTools.Core.SaveWxSettingCommand.AppSecret">
            <summary>
            小程序密钥
            </summary>
        </member>
        <member name="P:ApiTools.Core.SaveWxSettingCommand.EnvVersion">
            <summary>
            版本
            </summary>
        </member>
        <member name="M:ApiTools.Core.BaseRepository`2.GetQueryable(System.Boolean)">
@@ -6700,6 +6990,46 @@
            错误信息
            </summary>
        </member>
        <member name="T:ApiTools.Core.SendRequest">
            <summary>
            发送订阅消息
            </summary>
        </member>
        <member name="P:ApiTools.Core.SendRequest.WxmpCode">
            <summary>
            小程序编号
            </summary>
        </member>
        <member name="P:ApiTools.Core.SendRequest.EnvVersion">
            <summary>
            要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"。默认是正式版。
            </summary>
        </member>
        <member name="P:ApiTools.Core.SendRequest.template_id">
            <summary>
            所需下发的订阅模板id
            </summary>
        </member>
        <member name="P:ApiTools.Core.SendRequest.page">
            <summary>
            点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转
            </summary>
        </member>
        <member name="P:ApiTools.Core.SendRequest.touser">
            <summary>
            接收者(用户)的 openid
            </summary>
        </member>
        <member name="P:ApiTools.Core.SendRequest.data">
            <summary>
            模板内容,格式形如{ "phrase3": { "value": "审核通过" }, "name1": { "value": "订阅" }, "date2": { "value": "2019-12-25 09:42" } }
            </summary>
        </member>
        <member name="T:ApiTools.Core.SendResponse">
            <summary>
            发送订阅消息-结果
            </summary>
        </member>
        <member name="T:ApiTools.Core.WxmpOptions">
            <summary>
            微信小程序配置
@@ -6744,6 +7074,13 @@
            <param name="command"></param>
            <returns></returns>
        </member>
        <member name="M:ApiTools.Core.WxmpUtils.WxSend(ApiTools.Core.SendRequest)">
            <summary>
            该接口用于发送订阅消息
            </summary>
            <param name="command"></param>
            <returns></returns>
        </member>
        <member name="T:ApiTools.Core.XmlDoc">
            <summary>
            注释文档
ApiTools.Core/Entities/WxmpUtils/WxSetting.cs
New file
@@ -0,0 +1,36 @@
using Furion.DatabaseAccessor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ApiTools.Core
{
    /// <summary>
    /// 微信配置
    /// </summary>
    public class WxSetting : CommonEntity<MasterDbContextLocator>
    {
        /// <summary>
        /// 编号
        /// </summary>
        public string Code { get; set; }
        /// <summary>
        /// 小程序ID
        /// </summary>
        public string AppId { get; set; }
        /// <summary>
        /// 小程序密钥
        /// </summary>
        public string AppSecret { get; set; }
        /// <summary>
        /// 版本
        /// </summary>
        public string EnvVersion { get; set; }
    }
}
ApiTools.Core/Enums/Resources/EnumResourceController.cs
@@ -33,6 +33,12 @@
        CommonServerSmsUtils,
        /// <summary>
        /// 微信工具
        /// </summary>
        [ResourceController(EnumResourceService.CommonServer, "Wx")]
        CommonServerWxUtils,
        /// <summary>
        /// 用户认证
        /// </summary>
        [ResourceController(EnumResourceService.UserServer, "Auth")]
ApiTools.Core/Jobs/RefreshChannelWalletTransactionStatusJob.cs
@@ -33,11 +33,15 @@
            if (env != "Local")
            {
                var transactions = await channelWalletTransactionRepository.GetQueryable(false)
                .Include(it => it.PingAnPay)
                .Where(it =>
                    it.TransactionStatus == EnumWalletTransactionStatus.WaitPay
                    || it.TransactionStatus == EnumWalletTransactionStatus.Dealing)
                    || it.TransactionStatus == EnumWalletTransactionStatus.Dealing
                    || it.TransactionStatus == EnumWalletTransactionStatus.Success
                    && it.EreceiptStatus != EnumWalletTransactionEreceiptStatus.SUCCESS
                    && it.EreceiptStatus != EnumWalletTransactionEreceiptStatus.FAIL)
                .ToListAsync();
                var walletIds = transactions.DistinctSelect(it => it.WalletId);
                var walletIds = transactions.Select(it => it.WalletId).Distinct().ToList();
                var wallets = await channelWalletRepository.GetQueryable(false)
                    .Where(it => walletIds.Contains(it.Id))
                    .ToListAsync();
@@ -46,8 +50,12 @@
                    var wallet = wallets.FirstOrDefault(it => it.Id == transaction.WalletId);
                    if (wallet != null)
                    {
                        // 查询交易详情
                        await channelWalletService.GetTransactionDetail(wallet, transaction);
                        if (transaction.TransactionStatus == EnumWalletTransactionStatus.WaitPay
                            || transaction.TransactionStatus == EnumWalletTransactionStatus.Dealing)
                        {
                            // 查询交易详情
                            await channelWalletService.GetTransactionDetail(wallet, transaction);
                        }
                        // 下载回单
                        await channelWalletService.DownloadEreceiptUrl(wallet, transaction);
                    }
ApiTools.Core/Models/ChannelWallets/Queries/GetChannelWalletTransactionsQuery.cs
New file
@@ -0,0 +1,208 @@
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ApiTools.Core
{
    /// <summary>
    /// 查询渠道钱包交易详情
    /// </summary>
    [Resource([EnumResourceController.UserServerChannelWallet], Method = EnumResourceMethod.Post)]
    public class GetChannelWalletTransactionsQuery : IRequest<GetChannelWalletTransactionsQueryResult>
    {
        /// <summary>
        /// 外部订单号
        /// </summary>
        public List<string> OutCodes { get; set; } = [];
    }
    /// <summary>
    /// 查询渠道钱包交易详情
    /// </summary>
    public class GetChannelWalletTransactionsQueryResult
    {
        /// <summary>
        /// 项
        /// </summary>
        public List<GetChannelWalletTransactionsQueryResultItem> Items { get; set; } = [];
    }
    /// <summary>
    /// 查询渠道钱包交易详情
    /// </summary>
    public class GetChannelWalletTransactionsQueryResultItem
    {
        /// <summary>
        /// 交易Id
        /// </summary>
        public Guid Id { get; set; }
        /// <summary>
        /// 类型
        /// </summary>
        public EnumWalletTransactionType Type { get; set; }
        /// <summary>
        /// 订单号
        /// </summary>
        public string Code { get; set; }
        /// <summary>
        /// 外部订单号
        /// </summary>
        public string OutCode { get; set; }
        /// <summary>
        /// 并发锁
        /// </summary>
        public string ConcurrencyLock { get; set; }
        /// <summary>
        /// 金额
        /// </summary>
        public decimal Amount { get; set; }
        /// <summary>
        /// 余额
        /// </summary>
        public decimal Balance { get; set; }
        /// <summary>
        /// 收支后余额
        /// </summary>
        public decimal AfterBalance { get; set; }
        /// <summary>
        /// 外部操作人Id
        /// </summary>
        public string OutOperatorId { get; set; }
        /// <summary>
        /// 操作时间
        /// </summary>
        public DateTime? OperatorTime { get; set; }
        /// <summary>
        /// 付款人账户
        /// </summary>
        public string PayerAccount { get; set; }
        /// <summary>
        /// 付款人名称
        /// </summary>
        public string PayerName { get; set; }
        /// <summary>
        /// 付款人开户行
        /// </summary>
        public string PayerBank { get; set; }
        /// <summary>
        /// 付款人支行
        /// </summary>
        public string PayerBankBranch { get; set; }
        /// <summary>
        /// 外部收款人Id
        /// </summary>
        public string OutReceiveId { get; set; }
        /// <summary>
        /// 收款人姓名
        /// </summary>
        public string ReceiveName { get; set; }
        /// <summary>
        /// 收款人身份证号
        /// </summary>
        public string ReceiveIdentity { get; set; }
        /// <summary>
        /// 收款账户
        /// </summary>
        public string ReceiveAccount { get; set; }
        /// <summary>
        /// 收款人开户行
        /// </summary>
        public string ReceiveBank { get; set; }
        /// <summary>
        /// 收款人支行
        /// </summary>
        public string ReceiveBankBranch { get; set; }
        /// <summary>
        /// 币种
        /// </summary>
        public string Currency { get; set; }
        /// <summary>
        /// 用途
        /// </summary>
        public string Purpose { get; set; }
        /// <summary>
        /// 备注
        /// </summary>
        public string Remark { get; set; }
        /// <summary>
        /// 查询到的订单状态为FAIL失败或REFUND退票时,返回错误代码
        /// </summary>
        public string ErrorCode { get; set; }
        /// <summary>
        /// 查询到的订单状态为FAIL失败或REFUND退票时,返回具体的原因。
        /// </summary>
        public string FailReason { get; set; }
        /// <summary>
        /// 订单支付时间
        /// </summary>
        public DateTime? TransDate { get; set; }
        /// <summary>
        /// 预计收费金额(元),转账到银行卡专用
        /// </summary>
        public decimal? OrderFee { get; set; }
        /// <summary>
        /// 电子收据Id
        /// </summary>
        public string EreceiptFileId { get; set; }
        /// <summary>
        /// 电子收据下载链接
        /// </summary>
        public string EreceiptDownloadUrl { get; set; }
        /// <summary>
        /// 电子收据下载链接
        /// </summary>
        public string EreceiptDownloadOssUrl { get; set; }
        /// <summary>
        /// 电子收据下载链接
        /// </summary>
        public string EreceiptDownloadOssFullUrl { get; set; }
        /// <summary>
        /// 电子收据状态
        /// </summary>
        public EnumWalletTransactionEreceiptStatus? EreceiptStatus { get; set; }
        /// <summary>
        /// 电子收据错误信息
        /// </summary>
        public string EreceiptErrorMessage { get; set; }
        /// <summary>
        /// 状态
        /// </summary>
        public EnumWalletTransactionStatus TransactionStatus { get; set; }
    }
}
ApiTools.Core/Models/WxmpUtils/Commands/SaveWxSendCommand.cs
New file
@@ -0,0 +1,51 @@
using MediatR;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ApiTools.Core
{
    /// <summary>
    /// 该接口用于发送订阅消息
    /// </summary>
    [Resource([EnumResourceController.CommonServerWxUtils], Method = EnumResourceMethod.Post)]
    public class SaveWxSendCommand : IRequest<string>
    {
        /// <summary>
        /// 小程序编号
        /// </summary>
        public string WxmpCode { get; set; }
        /// <summary>
        /// 要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"。默认是正式版。
        /// </summary>
        public string EnvVersion { get; set; }
        /// <summary>
        /// 所需下发的订阅模板id
        /// </summary>
        public string template_id { get; set; }
        /// <summary>
        /// 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转
        /// </summary>
        public string page { get; set; }
        /// <summary>
        /// 接收者(用户)的 openid
        /// </summary>
        public string touser { get; set; }
        /// <summary>
        /// 模板内容,格式形如{ "phrase3": { "value": "审核通过" }, "name1": { "value": "订阅" }, "date2": { "value": "2019-12-25 09:42" } }
        /// </summary>
        public object data { get; set; }
        public string miniprogram_state { get; set; }
        public string lang { get; set; }
    }
}
ApiTools.Core/Models/WxmpUtils/Commands/SaveWxSettingCommand.cs
New file
@@ -0,0 +1,36 @@
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ApiTools.Core
{
    /// <summary>
    /// 保存微信配置
    /// </summary>
    [Resource([EnumResourceController.CommonServerWxUtils], Method = EnumResourceMethod.Post)]
    public class SaveWxSettingCommand : IRequest<Guid>
    {
        /// <summary>
        /// 编号
        /// </summary>
        public string Code { get; set; }
        /// <summary>
        /// 小程序ID
        /// </summary>
        public string AppId { get; set; }
        /// <summary>
        /// 小程序密钥
        /// </summary>
        public string AppSecret { get; set; }
        /// <summary>
        /// 版本
        /// </summary>
        public string EnvVersion { get; set; }
    }
}
ApiTools.Core/Utils/DbUtils/DbUtils.cs
@@ -10,6 +10,7 @@
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
@@ -604,7 +605,12 @@
                            ? p.IsModified
                            : true)
                        .ToDictionary(p => p.Metadata.Name, p => p.OriginalValue));
                Db.GetRepository<DbAuditLog, LogDbContextLocator>().InsertNow(log);
                // 创建独立作用域
                using (var scope = App.GetRequiredService<IServiceScopeFactory>().CreateScope())
                {
                    var rep = scope.ServiceProvider.GetRequiredService<IRepository<DbAuditLog, LogDbContextLocator>>();
                    rep.InsertNow(log);
                }
            }
        }
    }
ApiTools.Core/Utils/WxmpUtils/WxmpGetQrCodeRequest.cs
@@ -106,4 +106,73 @@
        public string ErrorMessage { get; set; }
    }
    /// <summary>
    /// 发送订阅消息
    /// </summary>
    public class SendRequest
    {
        /// <summary>
        /// 小程序编号
        /// </summary>
        [JsonProperty("WxmpCode")]
        [Required]
        public string WxmpCode { get; set; }
        /// <summary>
        /// 要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"。默认是正式版。
        /// </summary>
        [JsonProperty("env_version")]
        public string EnvVersion { get; set; }
        /// <summary>
        /// 所需下发的订阅模板id
        /// </summary>
        [JsonProperty("template_id")]
        [Required]
        public string template_id { get; set; }
        /// <summary>
        /// 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转
        /// </summary>
        [JsonProperty("page")]
        [Required]
        public string page { get; set; }
        /// <summary>
        /// 接收者(用户)的 openid
        /// </summary>
        [JsonProperty("touser")]
        [Required]
        public string touser { get; set; }
        /// <summary>
        /// 模板内容,格式形如{ "phrase3": { "value": "审核通过" }, "name1": { "value": "订阅" }, "date2": { "value": "2019-12-25 09:42" } }
        /// </summary>
        [JsonProperty("data")]
        [Required]
        public object data { get; set; }
        [JsonProperty("跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版")]
        [Required]
        public string miniprogram_state { get; set; }
        [JsonProperty("进入小程序查看”的语言类型,支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),默认为zh_CN")]
        [Required]
        public string lang { get; set; }
    }
    /// <summary>
    /// 发送订阅消息-结果
    /// </summary>
    public class SendResponse
    {
        [JsonProperty("ErrorCode")]
        public int ErrorCode { get; set; }
        [JsonProperty("errmsg")]
        public int errmsg { get; set; }
    }
}
ApiTools.Core/Utils/WxmpUtils/WxmpUtils.cs
@@ -1,6 +1,7 @@
using Aliyun.OSS;
using Azure.Core;
using Aop.Api.Domain;
using ApiTools.Core.Utils.WxmpUtils;
using Azure.Core;
using Furion.FriendlyException;
using Furion.HttpRemote;
using Mapster;
@@ -124,5 +125,42 @@
            var result = AliyunOSSUtils.Upload(command.OssScene, stream, command.OssFileName);
            return result.Url;
        }
        /// <summary>
        /// 该接口用于发送订阅消息
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        public async Task<string> WxSend(SendRequest command)
        {
            var option = options.Value.Items.FirstOrDefault(it => it.Code == command.WxmpCode);
            if (option == null || option.AppId.IsNull() || option.AppSecret.IsNull())
                throw Oops.Oh(EnumErrorCodeType.s400, "获取小程序码失败,缺失配置:WxmpOptions");
            command.EnvVersion = option.EnvVersion;
            var accessToken = await GetAccessToken(command.WxmpCode);
            var request = command.Adapt<SendRequest>();
            var jsonContent = JsonConvert.SerializeObject(request, new JsonSerializerSettings
            {
                NullValueHandling = NullValueHandling.Ignore
            });
            var response = await httpRemoteService.PostAsync("https://api.weixin.qq.com/cgi-bin/message/subscribe/send",
                builder => builder
                .WithQueryParameter("access_token", accessToken)
                .SetJsonContent(jsonContent));
            response.EnsureSuccessStatusCode();
            if (response.Content.Headers.ContentType.ToString() == "application/json; charset=UTF-8")
            {
                var jsonResult = await response.Content.ReadAsStringAsync();
                var callback = jsonResult.JsonTo<SendResponse>();
                if (callback == null || callback.ErrorCode != 0)
                    //throw Oops.Oh(EnumErrorCodeType.s510, $"获取小程序码失败:{callback.errmsg},请联系管理员");
                return jsonResult;
            }
            //var stream = await response.Content.ReadAsStreamAsync();
            //var result = AliyunOSSUtils.Upload(command, stream, command.OssFileName);
            return "";
        }
    }
}