Asp.Net Core3.0 微信小程序统一下单、退款、转账


微信统一下单开发文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1

微信支付小程序支付文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml

微信支付申请退款文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_8_11.shtml

微信支付付款到零钱开发文档:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2

简单记录一下net core3.0对接微信支付的相关代码,网上有很多微信支付的文章,但是一定要仔细看一遍微信的官方文档。

准备工作:

首先要开通小程序的支付能力,按要求提交审核材料,审核通过后,我们可以得到APPID、微信支付商户号mch_id、API密钥key、Appsecret,详见 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=3_1

之后在商户后台绑定同一主体的APPID并授权,发起授权后,商户需要自行前往对应平台确认授权申请。

最后在商户后台设置回调地址。

一、微信支付

1.支付V3

//支付接口 
 public async Task> unifiedorder(int money)
{
  var url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; var req = new GenerateOrderModelForWxPay { appid = _appid, mchid = _mchid, description = "XXXXX支付交易单", amount = new WxPayAmountModel { total = money, currency = "CNY" }, payer = new WxPayerModel { openid = "付款人的openid" }, out_trade_no = DateTime.Now.ToString("yyyyMMddhhmmss"), notify_url = "http://api.xxxx.com/api/pay/NotifyUrl" //支付回调,自己创建 }; _log.WriteLog(JsonConvert.SerializeObject(req)); var httpHandler = new HttpHandler(_mchid, _serialNo); HttpClient client = new HttpClient(httpHandler); var bodyJson = new StringContent(JsonConvert.SerializeObject(req), Encoding.UTF8, "application/json"); //一定要这样传递参数,不然在加密签名的时候获取到的参数就是\\u0这种形式的数据了,不是传递的这样的数据了,导致加密的结果不正确 var response = await client.PostAsync(url, bodyJson); var respStr = await response.Content.ReadAsStringAsync();//读取统一下单之后的返回结果,这样读取出来的直接就是结果,或者错误原因。这里面就包含prepay_id了

        var timeStamp = DateTimeOffset.Now.ToUnixTimeSeconds();
        var nonceStr = Path.GetRandomFileName();
        string message = $"{_appid}\n{timeStamp}\n{nonceStr}\nprepay_id={JsonConvert.DeserializeObject(respStr).prepay_id}\n";
        string signature = httpHandler.Sign(message);
        var newObj = new
        {
        timeStamp= timeStamp.ToString(),
        nonceStr = nonceStr,
        package= "prepay_id="+JsonConvert.DeserializeObject(respStr).prepay_id,
        paySign = signature
        };
        return new ResponseResult()
        {
        Code = Constant.Success,
        Data=JsonConvert.SerializeObject(newObj),
        };

}

//model

public class WxPayAmountModel

{
  public int total { get; set; }
  public string currency { get; set; }
}

public class WxPayerModel
{
  public string openid { get; set; }
}

///


/// 支付resource
///

public class GenerateOrderModelForWxPay
{
  public string appid { get; set; }
  public string mchid { get; set; }
  public string description { get; set; }
  public WxPayAmountModel amount { get; set; }
  public WxPayerModel payer { get; set; }
  public string out_trade_no { get; set; }
  public string notify_url { get; set; }
}

支付完成后,微信返回的结果格式如下:


   
   
   
   
   
   
   
   
   
   

2.支付结果通知:

支付完成后,微信会把相关支付结果及用户信息通过数据流的形式发送给商户,商户需要接收处理,并按文档规范返回应答。

通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)这里通知发送可能会多台服务器进行发送,且发送时间可能会在几秒内,但微信不保证通知最终一定能成功。

支付成功后,微信会将结果通知商户后台,即支付代码里的notify_url链接,该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。

微信调用回调地址返回数据格式:
<xml>
  <appid>wx2421b1c4370ec43b]]>appid>
  <attach>支付测试]]>attach>
  <bank_type>CFT]]>bank_type>
  <fee_type>CNY]]>fee_type>
  <is_subscribe>Y]]>is_subscribe>
  <mch_id>10000100]]>mch_id>
  <nonce_str>5d2b6c2a8db53831f7eda20af46e531c]]>nonce_str>
  <openid>oUpF8uMEb4qRXf22hE3X68TekukE]]>openid>
  <out_trade_no>1409811653]]>out_trade_no>
  <result_code>SUCCESS]]>result_code>
  <return_code>SUCCESS]]>return_code>
  <sign>B552ED6B279343CB493C5DD0D78AB241]]>sign>
  <time_end>20140903131540]]>time_end>
  <total_fee>1total_fee>
  <coupon_fee>10]]>coupon_fee>
  <coupon_count>1]]>coupon_count>
  <coupon_type>CASH]]>coupon_type>
  <coupon_id>10000]]>coupon_id>
  <trade_type>JSAPI]]>trade_type>
  <transaction_id>1004400740201409030005092168]]>transaction_id>
xml>

获取到微信的通知后,商户处理业务逻辑后要同步返回给微信结果,SUCCESS表示商户接收通知成功并校验成功。

 public dynamic NotifyUrl(NotifyDto ret)
        {
            WxPayAPI.WxPayData res = new WxPayAPI.WxPayData();
            res.SetValue("return_code", "FAIL");
            res.SetValue("return_msg", "订单查询失败");

            try
            {
                if (ret.Event_type == "TRANSACTION.SUCCESS")//支付成功
                {
                    //解密数据报文
                    var dataJson = AesGcmHelper.AesGcmDecryptFromBase64(_key, ret.Resource.Nonce, ret.Resource.Ciphertext, ret.Resource.Associated_data);
                    //转换对象接受
                    var data = JsonConvert.DeserializeObject(dataJson);
                    //处理自己的业务逻辑
                     TODO...

                    res.SetValue("return_code", "SUCCESS");
                    res.SetValue("return_msg", "OK");
                    return Content(res.ToXml());
                }
                else
                {
                    _log.WriteLog("NotifyFaile:" + JsonConvert.SerializeObject(ret));

                }
                return Content(res.ToXml());
            }
            catch (Exception ex)
            {
                _log.WriteLog(ex.ToString());
                return Content(res.ToXml());
            }
        }

/// 支付结果回调接收参数
public class NotifyDto
{
/// 通知ID通知的唯一ID
/// 示例值:EV-2018022511223320873
public string Id { get; set; }
/// 通知创建时间 通知创建的时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss.表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示北京时间2015年05月20日13点29分35秒。
/// 示例值:2015-05-20T13:29:35+08:00
public string Create_time { get; set; }
/// 通知类型 通知的类型,支付成功通知的类型为TRANSACTION.SUCCESS
/// 示例值:TRANSACTION.SUCCESS
public string Event_type { get; set; }
/// 通知数据类型 通知的资源数据类型,支付成功通知为encrypt-resource
/// 示例值:encrypt-resource
public string Resource_type { get; set; }
/// 通知数据 通知资源数据
/// json格式,见示例
public Resource Resource { get; set; }
/// 回调摘要
/// 示例值:支付成功

public string Summary { get; set; }
}

//回报文model

public class PayCipherText
{
public string out_trade_no { get; set; }
public string transaction_id { get; set; }
public string trade_type { get; set; }
public string trade_state { get; set; }
public string trade_state_desc { get; set; }
public string bank_type { get; set; }
public string success_time { get; set; }
public WxPayAmountModel amount { get; set; }
}

这里用到证书和回调报文解密,参考https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/zheng-shu-he-hui-tiao-bao-wen-jie-mi

二、退款

1.申请退款V3

请求URL:https://api.mch.weixin.qq.com/v3/refund/domestic/refunds

请求方式:POST

当交易发生之后一年内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付金额退还给买家,微信支付将在收到退款请求并且验证成功之后,将支付款按原路退还至买家账号上。

 
//微信退款
public async Task Refund() { //微信退费操作 var url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"; int money = 0; var retParamar = new { //transaction_id = "", out_trade_no = "", out_refund_no = "", reason = "客户申请提现,小程序退款", amount = new { refund = money, total = money, currency = "CNY" }, notify_url = "http://api.xxxx.com/api/pay/RefundNotifyUrl" //回调地址 }; var reqJson = JsonConvert.SerializeObject(retParamar); var httpHandler = new HttpHandler(_mchid, _serialNo); HttpClient client = new HttpClient(httpHandler); var bodyJson = new StringContent(reqJson, Encoding.UTF8, "application/json"); var response = await client.PostAsync(url, bodyJson); var respStr = await response.Content.ReadAsStringAsync(); var respResult = JsonConvert.DeserializeObject<dynamic>(respStr);
      //处理自己业务逻辑
      TODO...
}

2.退款回调V3

  public dynamic RefundNotifyUrl(NotifyDto ret)
        {
            WxPayAPI.WxPayData res = new WxPayAPI.WxPayData();
            res.SetValue("return_code", "FAIL");
            res.SetValue("return_msg", "失败");
            try
            {
                if (ret.Event_type == "REFUND.SUCCESS")//成功
                {
                    //解密数据报文
                    var dataJson = AesGcmHelper.AesGcmDecryptFromBase64(_key, ret.Resource.Nonce, ret.Resource.Ciphertext, ret.Resource.Associated_data);
                    //转换对象接受
                    var data = JsonConvert.DeserializeObject(dataJson);
                   //处理自己逻辑
            TODO...
res.SetValue("return_code", "SUCCESS"); res.SetValue("return_msg", "成功"); return Content(res.ToXml()); } else { _log.WriteLog("NotifyFaile:" + JsonConvert.SerializeObject(ret)); } return Content(res.ToXml()); } catch (Exception ex) { _log.WriteLog(ex.ToString()); return Content(res.ToXml()); } }

///


/// 退款resource
///

public class RefundCipherText
{
  public string mchid { get; set; }
  public WxRefundAmountModel amount { get; set; }
  public string out_trade_no { get; set; }
  public string transaction_id { get; set; }
  public string refund_id { get; set; }
  public string refund_status { get; set; }
  public string user_received_account { get; set; }
  public string success_time { get; set; }

}

 

 三、微信转账到零钱

TODO...