zhangwei
3 天以前 019b6cf4ccaa06fc5ca8f5dc5663975eb027d360
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
 
using Aop.Api;
using Aop.Api.Domain;
using Aop.Api.Request;
using Aop.Api.Response;
using Aop.Api.Util;
using Microsoft.AspNetCore.Hosting;
using NewLife.Reflection;
 
namespace Admin.NET.Core.Service;
 
/// <summary>
/// 支付宝支付服务 🧩
/// </summary>
[ApiDescriptionSettings(Order = 240)]
public class SysAlipayService : IDynamicApiController, ITransient
{
    private readonly IWebHostEnvironment _webHostEnvironment;
    private readonly SysConfigService _sysConfigService;
    private readonly List<IAopClient> _alipayClientList;
    private readonly IHttpContextAccessor _httpContext;
    private readonly AlipayOptions _option;
    private readonly ISqlSugarClient _db;
 
    public SysAlipayService(
        ISqlSugarClient db,
        IHttpContextAccessor httpContext,
        SysConfigService sysConfigService,
        IWebHostEnvironment webHostEnvironment,
        IOptions<AlipayOptions> alipayOptions)
    {
        _db = db;
        _httpContext = httpContext;
        _sysConfigService = sysConfigService;
        _option = alipayOptions.Value;
        _webHostEnvironment = webHostEnvironment;
 
        // 初始化支付宝客户端列表
        _alipayClientList = [];
        foreach (var account in _option.AccountList) _alipayClientList.Add(_option.GetClient(account));
    }
 
    /// <summary>
    /// 获取授权信息 🔖
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [NonUnify]
    [AllowAnonymous]
    [DisplayName("获取授权信息")]
    [ApiDescriptionSettings(Name = "AuthInfo"), HttpGet]
    public ActionResult GetAuthInfo([FromQuery] AlipayAuthInfoInput input)
    {
        var type = input.UserId?.Split('-').FirstOrDefault().ToInt();
        var userId = input.UserId?.Split('-').LastOrDefault().ToLong();
        var account = _option.AccountList.FirstOrDefault();
        var alipayClient = _alipayClientList.First();
 
        // 当前网页接口地址
        var currentUrl = $"{_option.AppAuthUrl}{_httpContext.HttpContext!.Request.Path}?userId={input.UserId}";
        if (string.IsNullOrEmpty(input.AuthCode))
        {
            // 重新授权
            var url = $"{_option.AuthUrl}?app_id={account!.AppId}&scope=auth_user&redirect_uri={currentUrl}";
            return new RedirectResult(url);
        }
 
        // 组装授权请求参数
        AlipaySystemOauthTokenRequest request = new()
        {
            GrantType = AlipayConst.GrantType,
            Code = input.AuthCode
        };
        AlipaySystemOauthTokenResponse response = alipayClient.CertificateExecute(request);
 
        // token换取用户信息
        AlipayUserInfoShareRequest infoShareRequest = new();
        AlipayUserInfoShareResponse info = alipayClient.CertificateExecute(infoShareRequest, response.AccessToken);
 
        // 记录授权信息
        var entity = _db.Queryable<SysAlipayAuthInfo>().First(u =>
            (!string.IsNullOrWhiteSpace(u.UserId) && u.UserId == info.UserId) ||
            (!string.IsNullOrWhiteSpace(u.OpenId) && u.OpenId == info.OpenId)) ?? new();
        entity.Copy(info, excludes: [nameof(SysAlipayAuthInfo.Gender), nameof(SysAlipayAuthInfo.Age)]);
        entity.Age = int.Parse(info.Age);
        entity.Gender = info.Gender switch
        {
            "m" => GenderEnum.Male,
            "f" => GenderEnum.Female,
            _ => GenderEnum.Unknown
        };
        entity.AppId = account!.AppId;
        if (entity.Id <= 0) _db.Insertable(entity).ExecuteCommand();
        else _db.Updateable(entity).ExecuteCommand();
 
        // 执行完,重定向到指定界面
        //var authPageUrl = _sysConfigService.GetConfigValueByCode<string>(ConfigConst.AlipayAuthPageUrl + type).Result;
        //return new RedirectResult(authPageUrl);
        return new RedirectResult(_option.AppAuthUrl + "/index.html");
    }
 
    /// <summary>
    /// 支付回调 🔖
    /// </summary>
    /// <returns></returns>
    [AllowAnonymous]
    [DisplayName("支付回调")]
    [ApiDescriptionSettings(Name = "Notify"), HttpPost]
    public string Notify()
    {
        SortedDictionary<string, string> sorted = [];
        foreach (string key in _httpContext.HttpContext!.Request.Form.Keys)
            sorted.Add(key, _httpContext.HttpContext.Request.Form[key]);
 
        var account = _option.AccountList.FirstOrDefault();
        string alipayPublicKey = Path.Combine(_webHostEnvironment.ContentRootPath, account!.AlipayPublicCertPath!.Replace('/', '\\').TrimStart('\\'));
        bool signVerified = AlipaySignature.RSACertCheckV1(sorted, alipayPublicKey, "UTF-8", account.SignType); // 调用SDK验证签名
        if (!signVerified) throw Oops.Oh("交易失败");
 
        // 更新交易记录
        var outTradeNo = sorted.GetValueOrDefault("out_trade_no");
        var transaction = _db.Queryable<SysAlipayTransaction>().First(x => x.OutTradeNo == outTradeNo) ?? throw Oops.Oh("交易记录不存在");
        transaction.TradeNo = sorted.GetValueOrDefault("trade_no");
        transaction.TradeStatus = sorted.GetValueOrDefault("trade_status");
        transaction.FinishTime = sorted.ContainsKey("gmt_payment") ? DateTime.Parse(sorted.GetValueOrDefault("gmt_payment")) : null;
        transaction.BuyerLogonId = sorted.GetValueOrDefault("buyer_logon_id");
        transaction.BuyerUserId = sorted.GetValueOrDefault("buyer_user_id");
        transaction.SellerUserId = sorted.GetValueOrDefault("seller_id");
        transaction.Remark = sorted.GetValueOrDefault("remark");
        _db.Updateable(transaction).ExecuteCommand();
 
        return "success";
    }
 
    /// <summary>
    ///  统一收单下单并支付页面接口 🔖
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("统一收单下单并支付页面接口")]
    [ApiDescriptionSettings(Name = "AlipayTradePagePay"), HttpPost]
    public string AlipayTradePagePay(AlipayTradePagePayInput input)
    {
        // 创建交易记录,状态为等待支付
        var transactionRecord = new SysAlipayTransaction
        {
            AppId = _option.AccountList.First().AppId,
            OutTradeNo = input.OutTradeNo,
            TotalAmount = input.TotalAmount.ToDecimal(),
            TradeStatus = "WAIT_PAY", // 等待支付
            CreateTime = DateTime.Now,
            Subject = input.Subject,
            Body = input.Body,
            Remark = "等待用户支付"
        };
        _db.Insertable(transactionRecord).ExecuteCommand();
 
        // 设置支付页面请求,并组装业务参数model,设置异步通知接收地址
        AlipayTradeWapPayRequest request = new();
        request.SetBizModel(new AlipayTradeWapPayModel()
        {
            Subject = input.Subject,
            OutTradeNo = input.OutTradeNo,
            TotalAmount = input.TotalAmount,
            Body = input.Body,
            ProductCode = "QUICK_WAP_WAY",
            TimeExpire = input.TimeoutExpress
        });
        request.SetNotifyUrl(_option.NotifyUrl);
 
        var alipayClient = _alipayClientList.First();
        var response = alipayClient.SdkExecute(request);
        if (response.IsError) throw Oops.Oh(response.SubMsg);
        return $"{_option.ServerUrl}?{response.Body}";
    }
 
    /// <summary>
    ///  交易预创建 🔖
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("交易预创建")]
    [ApiDescriptionSettings(Name = "AlipayPreCreate"), HttpPost]
    public string AlipayPreCreate(AlipayPreCreateInput input)
    {
        // 创建交易记录,状态为等待支付
        var transactionRecord = new SysAlipayTransaction
        {
            AppId = _option.AccountList.First().AppId,
            OutTradeNo = input.OutTradeNo,
            TotalAmount = input.TotalAmount.ToDecimal(),
            TradeStatus = "WAIT_PAY", // 等待支付
            CreateTime = DateTime.Now,
            Subject = input.Subject,
            Remark = "等待用户支付"
        };
        _db.Insertable(transactionRecord).ExecuteCommand();
 
        // 设置异步通知接收地址,并组装业务参数model
        AlipayTradePrecreateRequest request = new();
        request.SetNotifyUrl(_option.NotifyUrl);
        request.SetBizModel(new AlipayTradePrecreateModel()
        {
            Subject = input.Subject,
            OutTradeNo = input.OutTradeNo,
            TotalAmount = input.TotalAmount,
            TimeoutExpress = input.TimeoutExpress
        });
 
        var alipayClient = _alipayClientList.First();
        var response = alipayClient.CertificateExecute(request);
        if (response.IsError) throw Oops.Oh(response.SubMsg);
        return response.QrCode;
    }
 
    /// <summary>
    /// 单笔转账到支付宝账户
    ///  https://opendocs.alipay.com/open/62987723_alipay.fund.trans.uni.transfer
    /// </summary>
    [NonAction]
    public async Task<AlipayFundTransUniTransferResponse> Transfer(AlipayFundTransUniTransferInput input)
    {
        var account = _option.AccountList.FirstOrDefault(u => u.AppId == input.AppId) ?? throw Oops.Oh("未找到商户支付宝账号");
        var alipayClient = _option.GetClient(account);
 
        // 构造请求参数以调用接口
        AlipayFundTransUniTransferRequest request = new();
        AlipayFundTransUniTransferModel model = new()
        {
            BizScene = AlipayConst.BizScene,
            ProductCode = AlipayConst.ProductCode,
            OutBizNo = input.OutBizNo, // 商家订单
            TransAmount = $"{input.TransAmount}:F2", // 订单总金额
            OrderTitle = input.OrderTitle, // 业务标题
            Remark = input.Remark, // 业务备注
            PayeeInfo = new() // 收款方信息
            {
                CertType = input.CertType?.ToString(),
                CertNo = input.CertNo,
                Identity = input.Identity,
                Name = input.Name,
                IdentityType = input.IdentityType.ToString()
            },
            BusinessParams = input.PayerShowNameUseAlias ? "{\"payer_show_name_use_alias\":\"true\"}" : null
        };
 
        request.SetBizModel(model);
        var response = alipayClient.CertificateExecute(request);
 
        // 保存转账记录
        await _db.Insertable(new SysAlipayTransaction
        {
            UserId = input.UserId,
            AppId = input.AppId,
            TradeNo = response.OrderId,
            OutTradeNo = input.OutBizNo,
            TotalAmount = response.Amount.ToDecimal(),
            TradeStatus = response.Code == "10000" ? "SUCCESS" : "FAILED",
            Subject = input.OrderTitle,
            ErrorInfo = response.SubMsg,
            Remark = input.Remark
        }).ExecuteCommandAsync();
 
        return response;
    }
}