(精华)2020年10月28日 支付宝 ASP.NET Core 使用支付宝PC网站支付

前言

支付宝有比较多的支付产品,比如当面付、APP支付、手机网站支付、电脑网站支付等,本次讲的是电脑网站支付。在这里插入图片描述

创建项目

新建一个ASP.NET Core 3.1 MVC项目在这里插入图片描述

配置

由于我在开发的时候支付接口并没有申请下来,所以使用的是支付宝沙箱环境来进行开发的。

支付宝沙箱环境介绍:蚂蚁沙箱环境(Beta)是协助开发者进行接口功能开发及主要功能联调的辅助环境。沙箱环境模拟了开放平台部分产品的主要功能和主要逻辑,在开发者应用上线审核前,开发者可以根据自身需求,先在沙箱环境中了解、组合和调试各种开放接口,进行开发调通工作,从而帮助开发者在应用上线审核完成后,能更快速、更顺利的进行线上调试和验收工作。
如果在签约或创建应用前想要进行集成测试,可以使用沙箱环境。
沙箱环境支持使用个人账号或企业账号登陆。

沙箱环境地址:https://openhome.alipay.com/platform/appDaily.htm?tab=info
1.生成密钥
使用下载支付宝官方提供的密钥生成工具来进行生,详细介绍:https://doc.open.alipay.com/docs/doc.htm?treeId=291&articleId=105971&docType=1在这里插入图片描述
2.设置应用公钥
我们生成密钥之后,需要到支付宝后台设置应用公钥,就是我们生成的公钥。在这里插入图片描述
设置之后,支付宝会给我们一个支付宝公钥,保存这个支付宝公钥在这里插入图片描述
3.配置SDK
新建一个Config类,在里面存储我们的配置。

public class Config
{
	// 应用ID,您的APPID
	public static string AppId = "";

	// 支付宝网关
	public static string Gatewayurl = "";

	// 商户私钥,您的原始格式RSA私钥
	public static string PrivateKey = "";

	// 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
	public static string AlipayPublicKey = "";

	// 签名方式
	public static string SignType = "RSA2";

	// 编码格式
	public static string CharSet = "UTF-8";
}

应用ID和支付宝网关都可以在支付宝后台查看。在这里插入图片描述
商户私钥即我们自己生成的私钥,公钥就是支付宝公钥这里一定要注意,别用错了。这里的公钥私钥直接填写字符串即可。

签名方式推荐使用RSA2,使用RSA2,支付宝会用SHA256withRsa算法进行接口调用时的验签(不限制密钥长度)。

编码格式,如果我们是直接配置的字符串(公钥、私钥),那么就是我们代码的编码,如果使用的是文件(公钥、私钥),那么就是文件的编码。

完成配置如下:在这里插入图片描述
添加SDK
官方SDK的源码(.NET Framework),用.NET Standard 2.0 实现的支付宝服务端SDK,Alipay.AopSdk.Core(github:https://github.com/stulzq/Alipay.AopSdk.Core) ,支持.NET Core 2.0。
通过Nuget安装:Install-Package Alipay.AopSdk.Core在这里插入图片描述
支付
添加一个控制器 PayController

/// <summary>
/// 支付首页
/// </summary>
/// <returns></returns>
public IActionResult Index()
{
	return View();
}
/// 发起支付请求
/// </summary>
/// <param name="tradeno">外部订单号,商户网站订单系统中唯一的订单号</param>
/// <param name="subject">订单名称</param>
/// <param name="totalAmout">付款金额</param>
/// <param name="itemBody">商品描述</param>
/// <returns></returns>
[HttpPost]
public void PayRequest(string tradeno,string subject,string totalAmout,string itemBody)
{
	DefaultAopClient client = new DefaultAopClient(Config.Gatewayurl, Config.AppId, Config.PrivateKey, "json", "2.0",
		Config.SignType, Config.AlipayPublicKey, Config.CharSet, false);

	// 组装业务参数model
	AlipayTradePagePayModel model = new AlipayTradePagePayModel();
	model.Body = itemBody;
	model.Subject = subject;
	model.TotalAmount = totalAmout;
	model.OutTradeNo = tradeno;
	model.ProductCode = "FAST_INSTANT_TRADE_PAY";

	AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
	// 设置同步回调地址
	request.SetReturnUrl("http://localhost:5000/Pay/Callback");
	// 设置异步通知接收地址
	request.SetNotifyUrl("");
	// 将业务model载入到request
	request.SetBizModel(model);

	var response = client.SdkExecute(request);
	Console.WriteLine($"订单支付发起成功,订单号:{tradeno}");
	//跳转支付宝支付
	Response.Redirect(Config.Gatewayurl + "?" + response.Body);
}

设置首页html

@{ 
    ViewData["Title"] = "支付宝PC端支付";
    Layout = null;
}
<h2>支付宝PC端支付</h2>
<div class="row">
    <div class="col-sm-12" s>
        <form asp-action="PayRequest" method="post" class="form-horizontal" role="form">
            <div class="form-group">
                <label for="tradeno" class="control-label col-sm-2">商户订单号:</label>
                <div class="col-sm-10">
                    <input type="text" name="tradeno" class="form-control" id="tradeno" value="" />
                </div>
            </div>

            <div class="form-group">
                <label for="subject" class="control-label col-sm-2">订单名称:</label>
                <div class="col-sm-10">
                    <input type="text" name="subject" class="form-control" id="subject" value="" />
                </div>
            </div>

            <div class="form-group">
                <label for="totalAmout" class="control-label col-sm-2">付款金额:</label>
                <div class="col-sm-10">
                    <input type="number" min="0.01" name="totalAmout" class="form-control" id="totalAmout" value="" />
                </div>
            </div>

            <div class="form-group">
                <label for="itemBody" class="control-label col-sm-2">商品描述:</label>
                <div class="col-sm-10">
                    <input type="text" name="itemBody" class="form-control" id="itemBody" value="" />
                </div>
            </div>

            <div class="form-group">
                <div class="col-sm-10 col-sm-offset-2">
                    <button class="btn btn-success btn-block">付款</button>
                    <p class="help-block text-center">如果您点击“付款”按钮,即表示您同意该次的执行操作。</p>
                </div>

            </div>
        </form>
    </div>

</div>

<script>
    //初始化商户订单号
    function GetDateNow() {
        var vNow = new Date();
        var sNow = "";
        sNow += String(vNow.getFullYear());
        sNow += String(vNow.getMonth() + 1);
        sNow += String(vNow.getDate());
        sNow += String(vNow.getHours());
        sNow += String(vNow.getMinutes());
        sNow += String(vNow.getSeconds());
        sNow += String(vNow.getMilliseconds());
        document.getElementById("tradeno").value =  sNow;
    }
    GetDateNow();
</script>

运行:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

支付异步回调通知
支付宝同步回调通知(支付成功后跳转到商户网站),是不可靠的,所以这里必须使用异步通知来获取支付结果,异步通知即支付宝主动请求我们提供的地址,我们根据请求数据来校验,获取支付结果。

/// <summary>
/// 支付异步回调通知 需配置域名 因为是支付宝主动post请求这个action 所以要通过域名访问或者公网ip
/// </summary>
public async void Notify()
{
	/* 实际验证过程建议商户添加以下校验。
	1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
	2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
	3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
	4、验证app_id是否为该商户本身。
	*/
	Dictionary<string, string> sArray = GetRequestPost();
	if (sArray.Count != 0)
	{
		bool flag = AlipaySignature.RSACheckV1(sArray, Config.AlipayPublicKey,Config.CharSet, Config.SignType, false);
		if (flag)
		{
			//交易状态
			//判断该笔订单是否在商户网站中已经做过处理
			//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
			//请务必判断请求时的total_amount与通知时获取的total_fee为一致的
			//如果有做过处理,不执行商户的业务程序

			//注意:
			//退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
			Console.WriteLine(Request.Form["trade_status"]);

			await Response.WriteAsync("success");
		}
		else
		{
			await Response.WriteAsync("fail");
		}
	}
}

同步回调
同步回调即支付成功跳转回商户网站

运行:在这里插入图片描述

/// <summary>
/// 支付同步回调
/// </summary>
[HttpGet]
public  IActionResult Callback()
{
	/* 实际验证过程建议商户添加以下校验。
	1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
	2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
	3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
	4、验证app_id是否为该商户本身。
	*/
	Dictionary<string, string> sArray = GetRequestGet();
	if (sArray.Count != 0)
	{
		bool flag = AlipaySignature.RSACheckV1(sArray, Config.AlipayPublicKey, Config.CharSet, Config.SignType, false);
		if (flag)
		{
			Console.WriteLine($"同步验证通过,订单号:{sArray["out_trade_no"]}");
			ViewData["PayResult"] = "同步验证通过";
		}
		else
		{
			Console.WriteLine($"同步验证失败,订单号:{sArray["out_trade_no"]}");
			ViewData["PayResult"] = "同步验证失败";
		}
	}
	return View();
}

付款结果
在这里插入图片描述
在这里插入图片描述
订单查询
查询订单当前状态:已付款、未付款等等。

#region 订单查询
[HttpGet]
public IActionResult Query()
{
	return View();
}

[HttpPost]
public JsonResult Query(string tradeno, string alipayTradeNo)
{
	DefaultAopClient client = new DefaultAopClient(Config.Gatewayurl, Config.AppId, Config.PrivateKey, "json", "2.0",
		Config.SignType, Config.AlipayPublicKey, Config.CharSet, false);
	AlipayTradeQueryModel model = new AlipayTradeQueryModel();
	model.OutTradeNo = tradeno;
	model.TradeNo = alipayTradeNo;

	AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
	request.SetBizModel(model);

	var response = client.Execute(request);
	return Json(response.Body);
}

前端页面

@{
    ViewData["Title"] = "订单查询";
    Layout = null;
}

<h2>订单查询</h2>

<div class="row">
    <div class="col-sm-12" s>
        <form  class="form-horizontal" role="form">
            <div class="form-group">
                <label for="tradeno" class="control-label col-sm-2">商户订单号:</label>
                <div class="col-sm-10">
                    <input type="text" name="tradeno" class="form-control" id="tradeno" value="" />
                </div>
            </div>

            <div class="form-group">
                <label for="alipayTradeNo" class="control-label col-sm-2">支付宝交易号:</label>
                <div class="col-sm-10">
                    <input type="text" name="alipayTradeNo" class="form-control" id="alipayTradeNo" value="" />
                </div>
            </div>


            <div class="form-group">
                <div class="col-sm-10 col-sm-offset-2">
                    <button class="btn btn-success btn-block" type="button" id="btnQuery" onclick="queryOrder()">查询</button>
                    <p class="help-block text-center">商户订单号和支付宝交易号任意填一个均可。</p>
                </div>

            </div>
        </form>
    </div>
    
    <div class="col-sm-12" s>
        <form class="form-horizontal" role="form">
            <div class="form-group">
                <label for="tradeno" class="control-label col-sm-2">查询结果:</label>
                <div class="col-sm-10">
                    <textarea class="form-control" rows="20" id="queryResult"></textarea>
                    <a href="http://json.cn" target="_blank">Json格式化</a>
                </div>
            </div>

        </form>
    </div>

</div>

@section Scripts{
    <script>
        function queryOrder() {
            $.post("/Pay/Query",
                {
                    tradeno: $("#tradeno").val(),
                    alipayTradeNo: $("#alipayTradeNo").val()
                },
                function(data) {
                    $("#queryResult").html(data);
                });
        }
    </script>
}

订单退款
退回该订单金额。

[HttpGet]
public IActionResult Refund()
{
    return View();
}
/// <summary>
/// 订单退款
/// </summary>
/// <param name="tradeno">商户订单号</param>
/// <param name="alipayTradeNo">支付宝交易号</param>
/// <param name="refundAmount">退款金额</param>
/// <param name="refundReason">退款原因</param>
/// <param name="refundNo">退款单号</param>
/// <returns></returns>
[HttpPost]
public JsonResult Refund(string tradeno,string alipayTradeNo,string refundAmount,string refundReason,string refundNo)
{
	DefaultAopClient client = new DefaultAopClient(Config.Gatewayurl, Config.AppId, Config.PrivateKey, "json", "2.0",
		Config.SignType, Config.AlipayPublicKey, Config.CharSet, false);

	AlipayTradeRefundModel model = new AlipayTradeRefundModel();
	model.OutTradeNo = tradeno;
	model.TradeNo = alipayTradeNo;
	model.RefundAmount = refundAmount;
	model.RefundReason = refundReason;
	model.OutRequestNo = refundNo;

	AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
	request.SetBizModel(model);

	var response = client.Execute(request);
	return Json(response.Body);
}

退款前端页面

@{
    ViewData["Title"] = "订单退款";
    Layout = null;
}

<h2>订单退款</h2>

<div class="row">
    <div class="col-sm-12" s>
        <form class="form-horizontal" role="form">
            <div class="form-group">
                <label for="tradeno" class="control-label col-sm-2">商户订单号:</label>
                <div class="col-sm-10">
                    <input type="text" name="tradeno" class="form-control" id="tradeno" value="" />
                </div>
            </div>

            <div class="form-group">
                <label for="alipayTradeNo" class="control-label col-sm-2">支付宝交易号:</label>
                <div class="col-sm-10">
                    <input type="text" name="alipayTradeNo" class="form-control" id="alipayTradeNo" value="" />
                </div>
            </div>
            
            <div class="form-group">
                <label for="refundAmount" class="control-label col-sm-2">退款金额:</label>
                <div class="col-sm-10">
                    <input type="text" name="refundAmount" class="form-control" id="refundAmount" value=""  placeholder="不得超过订单金额"/>
                </div>
            </div>
            
            <div class="form-group">
                <label for="refundReason" class="control-label col-sm-2">退款原因:</label>
                <div class="col-sm-10">
                    <input type="text" name="refundReason" class="form-control" id="refundReason" value="" />
                </div>
            </div>
            
            <div class="form-group">
                <label for="refundno" class="control-label col-sm-2">退款单号:</label>
                <div class="col-sm-10">
                    <input type="text" name="refundno" class="form-control" id="refundno" value="" />
                </div>
            </div>


            <div class="form-group">
                <div class="col-sm-10 col-sm-offset-2">
                    <button class="btn btn-success btn-block" type="button" id="btnQuery" onclick="queryOrder()">查询</button>
                    <p class="help-block text-center">商户订单号和支付宝交易号任意填一个均可。</p>
                </div>

            </div>
        </form>
    </div>

    <div class="col-sm-12" s>
        <form class="form-horizontal" role="form">
            <div class="form-group">
                <label for="tradeno" class="control-label col-sm-2">查询结果:</label>
                <div class="col-sm-10">
                    <textarea class="form-control" rows="20" id="queryResult"></textarea>
                    <a href="http://json.cn" target="_blank">Json格式化</a>
                </div>
            </div>

        </form>
    </div>

</div>

@section Scripts{
    <script>
        function queryOrder() {
            $.post("/Pay/Refund",
                {
                    tradeno: $("#tradeno").val(),
                    alipayTradeNo: $("#alipayTradeNo").val(),
                    refundAmount: $("#refundAmount").val(),
                    refundReason: $("#refundReason").val(),
                    refundno: $("#refundno").val()
                },
                function(data) {
                    $("#queryResult").html(data);
                });
        }

    </script>
}

退款查询
查询退款信息。

/// <summary>
/// 退款查询
/// </summary>
/// <returns></returns>
public IActionResult RefundQuery()
{
    return View();
}
/// <summary>
/// 退款查询
/// </summary>
/// <param name="tradeno">商户订单号</param>
/// <param name="alipayTradeNo">支付宝交易号</param>
/// <param name="refundNo">退款单号</param>
/// <returns></returns>
[HttpPost]
public JsonResult RefundQuery(string tradeno,string alipayTradeNo,string refundNo)
{
	DefaultAopClient client = new DefaultAopClient(Config.Gatewayurl, Config.AppId, Config.PrivateKey, "json", "2.0",
		Config.SignType, Config.AlipayPublicKey, Config.CharSet, false);

	if (string.IsNullOrEmpty(refundNo))
	{
		refundNo = tradeno;
	}

	AlipayTradeFastpayRefundQueryModel model = new AlipayTradeFastpayRefundQueryModel();
	model.OutTradeNo = tradeno;
	model.TradeNo = alipayTradeNo;
	model.OutRequestNo = refundNo;

	AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
	request.SetBizModel(model);

	var response = client.Execute(request);
	return Json(response.Body);
}

退款查询前端页面

@{
    ViewData["Title"] = "退款查询";
    Layout = null;
}

<h2>退款查询</h2>

<div class="row">
    <div class="col-sm-12" s>
        <form class="form-horizontal" role="form">
            <div class="form-group">
                <label for="tradeno" class="control-label col-sm-2">商户订单号:</label>
                <div class="col-sm-10">
                    <input type="text" name="tradeno" class="form-control" id="tradeno" value="" />
                </div>
            </div>

            <div class="form-group">
                <label for="alipayTradeNo" class="control-label col-sm-2">支付宝交易号:</label>
                <div class="col-sm-10">
                    <input type="text" name="alipayTradeNo" class="form-control" id="alipayTradeNo" value="" />
                </div>
            </div>
            
            <div class="form-group">
                <label for="refundNo" class="control-label col-sm-2">退款单号:</label>
                <div class="col-sm-10">
                    <input type="text" name="refundNo" class="form-control" id="refundNo" value="" placeholder="如不填则为商户交易号" />
                </div>
            </div>


            <div class="form-group">
                <div class="col-sm-10 col-sm-offset-2">
                    <button class="btn btn-success btn-block" type="button" id="btnQuery" onclick="queryOrder()">查询</button>
                    <p class="help-block text-center">商户订单号和支付宝交易号任意填一个均可。</p>
                </div>

            </div>
        </form>
    </div>

    <div class="col-sm-12" s>
        <form class="form-horizontal" role="form">
            <div class="form-group">
                <label for="tradeno" class="control-label col-sm-2">查询结果:</label>
                <div class="col-sm-10">
                    <textarea class="form-control" rows="20" id="queryResult"></textarea>
                    <a href="http://json.cn" target="_blank">Json格式化</a>
                </div>
            </div>

        </form>
    </div>

</div>

@section Scripts{
    <script>
        function queryOrder() {
            $.post("/Pay/RefundQuery",
                {
                    tradeno: $("#tradeno").val(),
                    alipayTradeNo: $("#alipayTradeNo").val(),
                    refundNo: $("#refundNo").val()
                },
                function(data) {
                    $("#queryResult").html(data);
                });
        }
    </script>
}

订单关闭
对一定时间以后没有进行付款的订单进行关闭,订单状态需为:待付款,已完成支付的订单无法关闭。

public IActionResult OrderClose()
{
    return View();
}
/// <summary>
/// 关闭订单
/// </summary>
/// <param name="tradeno">商户订单号</param>
/// <param name="alipayTradeNo">支付宝交易号</param>
/// <returns></returns>
[HttpPost]
public JsonResult OrderClose(string tradeno, string alipayTradeNo)
{
	DefaultAopClient client = new DefaultAopClient(Config.Gatewayurl, Config.AppId, Config.PrivateKey, "json", "2.0",
		Config.SignType, Config.AlipayPublicKey, Config.CharSet, false);

	AlipayTradeCloseModel model = new AlipayTradeCloseModel();
	model.OutTradeNo = tradeno;
	model.TradeNo = alipayTradeNo;

	AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
	request.SetBizModel(model);

	var response = client.Execute(request);
	return Json(response.Body);
}

订单关闭前端页面

@{
    ViewData["Title"] = "订单关闭";
    Layout = null;
}

<h2>订单关闭</h2>

<div class="row">
    <div class="col-sm-12" s>
        <form class="form-horizontal" role="form">
            <div class="form-group">
                <label for="tradeno" class="control-label col-sm-2">商户订单号:</label>
                <div class="col-sm-10">
                    <input type="text" name="tradeno" class="form-control" id="tradeno" value="" />
                </div>
            </div>

            <div class="form-group">
                <label for="alipayTradeNo" class="control-label col-sm-2">支付宝交易号:</label>
                <div class="col-sm-10">
                    <input type="text" name="alipayTradeNo" class="form-control" id="alipayTradeNo" value="" />
                </div>
            </div>


            <div class="form-group">
                <div class="col-sm-10 col-sm-offset-2">
                    <button class="btn btn-success btn-block" type="button" id="btnQuery" onclick="queryOrder()">查询</button>
                    <p class="help-block text-center">商户订单号和支付宝交易号任意填一个均可。</p>
                </div>

            </div>
        </form>
    </div>

    <div class="col-sm-12" s>
        <form class="form-horizontal" role="form">
            <div class="form-group">
                <label for="tradeno" class="control-label col-sm-2">执行结果:</label>
                <div class="col-sm-10">
                    <textarea class="form-control" rows="20" id="queryResult"></textarea>
                    <a href="http://json.cn" target="_blank">Json格式化</a>
                </div>
            </div>

        </form>
    </div>

</div>

@section Scripts{
    <script>
        function queryOrder() {
            $.post("/Pay/OrderClose",
                {
                    tradeno: $("#tradeno").val(),
                    alipayTradeNo: $("#alipayTradeNo").val()
                },
                function(data) {
                    $("#queryResult").html(data);
                });
        }
    </script>
}

相关的解析参数函数

#region 解析请求参数
private Dictionary<string, string> GetRequestGet()
   {
    Dictionary<string, string> sArray = new Dictionary<string, string>();

    ICollection<string> requestItem = Request.Query.Keys;
    foreach (var item in requestItem)
    {
	    sArray.Add(item,Request.Query[item]);

    }
    return sArray;

   }

   private Dictionary<string, string> GetRequestPost()
   {
    Dictionary<string, string> sArray = new Dictionary<string, string>();

    ICollection<string> requestItem = Request.Form.Keys;
    foreach (var item in requestItem)
    {
	    sArray.Add(item, Request.Form[item]);

    }
    return sArray;

   }

#endregion
©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:上身试试 返回首页