5.微信支付 - ILazyBasicPayV2

概念

在接入微信支付过程中,会出现APPID、MCH_ID、公众平台、开放平台、商户平台等概念,下面仅从微信支付的角度来做简单分析:

image-20200809172027380

平台

● 公众平台(mp.weixin.qq.com):注册、配置服务号、订阅号、小程序的入口,注册成功后系统就会下发一个与之一一对应的APPID(其中订阅号的APPID不支持申请和使用微信支付)。

● 商户平台( pay.weixin.qq.com):微信支付业务管理中心,商户可以在商户平台进行所有支付业务相关操作,例如退款、下载对账单、查询订单、提现、账号绑定、API证书下载、API密钥设置、查看证书序列号等操作。

● 开放平台(open.weixin.qq.com):注册、配置APP移动应用、网站应用的入口,注册成功后系统就会下发一个与之一一对应的APPID。

参数

APPID:在公众平台或开放平台申请注册之后由平台下发,在支付接口中通常作为配置参数,必须上传。

MCH_ID: 微信支付商户号,在公众平台、开放平台申请微信支付成功后由微信支付下发,或者直接在商户平台注册也可获得MCH_ID,在支付接口中通常作为配置参数,必须上传。

支付接口要求APPID与MCH_ID必须有绑定关系,在商户平台注册获得的MCH_ID需要在【商户平台—>产品中心—>APPID授权管理】菜单下与APPID进行绑定后方可使用。

KEY:支付秘钥,设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置

配置员工账号

可参考如下链接配置员工账号

配置员工账号 :https://kf.qq.com/faq/140225MveaUz150107EvMfyq.html

开通支付产品

在产品中心, 根据需求开通需要的支付产品

image-20200723182856580

JSAPI支付 : GetJsApiScriptAsync

商户平台与公众平台配置

  • 登录到微信支付商户平台-> 产品中心-> 开发配置中, 配置JSAPI支付授权目录, 支付目录必须完全匹配(包括http和https)

  • 支付授权目录校验规则说明:

    1、如果支付授权目录设置为顶级域名(例如:https://www.weixin.com/ ),那么只校验顶级域名,不校验后缀;

    2、如果支付授权目录设置为多级目录,就会进行全匹配,例如设置支付授权目录为https://www.weixin.com/abc/123/,则实际请求页面目录不能为https://www.weixin.com/abc/,也不能为https://www.weixin.com/abc/123/pay/,必须为https://www.weixin.com/abc/123/

    具体配置参考 : https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3

image-20200809180432823

  • 登录到微信公众平台-> 设置-> 公众号设置, 配置网页安全域名
    • 业务域名 : 设置业务域名后,在微信内访问该域名下页面时,不会被重新排版。用户在该域名上进行输入时,不出现下图所示的安全提示。
    • JS接口安全域名 : 设置JS接口安全域名后,公众号开发者可在该域名下调用微信开放的JS接口
    • 网页安全域名 : 用户在网页授权页同意授权给公众号后,微信会将授权数据传给一个回调页面,回调页面需在此域名下,以确保安全可靠。

image-20200809181426678

使用LazyWeChat调用JSAPI支付

方法说明

  • 参数

    • out_trade_no : 商户订单号, 商户系统内部订单号(商户自定义单号规则即可), 要求32个字符内, 只能是数字、大小写字母_-|* 且在同一个商户号下唯一
    • body : 商品描述
    • total_fee : 总金额(单位 : 分)
    • openid : 购买者openid
    • notify_url : 通知地址, 异步接收微信支付结果通知的回调地址, 通知url必须为外网可访问的url, 不能携带参数, 在LazyWeChat中请使用服务器地址 http://domain.com/LazyWechatListener
  • 返回值 : 用于JSAPI支付的JS脚本

    <script type="text/javascript">
    function onBridgeReady()
    {
        WeixinJSBridge.invoke(
            'getBrandWCPayRequest',
            {
                "appId": "wxbb2xxxxxxxxb991d",
                "nonceStr": "2adce8d54016458da98a7876edf3d1c4",
                "package": "prepay_id=wx1421xxxxxxxx99a917b08facfb59330000",
                "paySign": "11B80E6F3Exxxxxxxx3B55CD1D30F5AA",
                "signType": "MD5",
                "timeStamp": "1597439963735"
            },
            function (res)
            {
                WeixinJSBridge.log(res.err_msg);
            }
        );
    }
    if (typeof WeixinJSBridge == 'undefined'){
        if (document.addEventListener)
        {
            document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
        }
        else if (document.attachEvent)
        {
            document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
            document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
        }
    }
    else{
        onBridgeReady();
    }
    </script>
    

示例代码

  • ConfigureServices中添加 services.AddLazyPay();
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddLazyPay();
}
  • 在Controller中生成JsApi支付的JS代码
using LazyWeChat.Abstract.WeChatPay.V2;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System;
using System.Threading.Tasks;

namespace WebTest.OfficialAccount.Controllers
{
    public class PayController : Controller
    {
        private readonly ILazyBasicPayV2 _lazyBasicPay;
        private Random random = new Random();
        public PayController(ILazyBasicPayV2 lazyBasicPay)
        {
        	_lazyBasicPay = lazyBasicPay;
        }

        public async Task<IActionResult> JsApi()
        {
            var out_trade_no = $"JsApi-{random.Next(0, 100000)}";
            var notify_url = "https://test.lazywechat.cn/LazyWechatListener";
            var js = await _lazyBasicPay.GetJsApiScriptAsync(out_trade_no, "Body desc for jsapi", 1, "oNDiC0d-r7Su5mYCU-mXFSXuhmtQ", notify_url);	//生成JsApi代码
            ViewBag.js = js;
            return View();
        }
    }
}

备注 : notify_url请指定为公众号的服务器URL地址, 设置回调地址,回调地址需返回如下xml格式数据(该xml已经在LazyWeChat服务器URL地址中集成),不返回的话,微信会向此地址不停的发送请求

<xml>
    <return_code><![CDATA[SUCCESS]]></return_code>
    <return_msg><![CDATA[OK]]></return_msg>
</xml>
  • 在cshtml中注册JsApi, 其他语言客户端请自行脑补
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>JsApi</title>
    <script type="text/javascript">
        @Html.Raw(ViewBag.js)
    </script>
</head>
<body>
    <h2>This is JsApi test page</h2>
</body>
</html>

调用接口弹出如下页面

image-20200724195006224

Native(扫码)支付

模式一 : GetPrePayQRCode

  • 需要登录商户平台 -> 产品中心 -> 开发配置 -> 支付配置 , 配置Native支付回调链接

  • 需在Startup.cs中配置中间件, 该中间件用于获取商品描述以及商品总金额

  • 在Controller中传递out_trade_no, 生成支付二维码

image-20200810093836768

在LazyWeChat中NativeNotifyListener是专门负责监听扫码支付模式一的URL, 在你的Native支付回调函数中便可配置为 http://xxxxxxx/NativeNotifyListener

在调用时仅传递out_trade_no(可认为是商品ID), 但是具体的商品信息以及商品金额,需要在后调函数中指定。

LazyWeChat中调用扫描支付模式一

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddLazyPay();				//添加微信支付相关服务
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(
    IApplicationBuilder app,
    IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
    	app.UseDeveloperExceptionPage();
    }
    else
    {
    	app.UseExceptionHandler("/Home/Error");
    }
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();
	
    //为扫描支付模式一配置中间件
    app.UseLazyNativePay((out_trade_no) =>
    { 
        //此处仅模拟生成商品描述以及商品金额, 真实情况应该是通过out_trade_no去相关数据库查询获取body与total_fee
        System.Random random = new System.Random();
        var body = $"test product body:{out_trade_no}-{random.Next(100000)}";
        var total_fee = 1;
        return (body, total_fee);
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Pay}/{action=GetNativePay1QRCode}/{id?}");
    });
}
  • services.AddLazyPay(); 添加微信支付相关的服务
  • UseLazyNativePay是专门针对扫码支付模式一的一个中间件, 参数为一个 Func<string, (string, int)>的委托, 这个委托的input参数为商品ID, output为一个元组, Item1为商品描述、Item2为商品金额,这个委托参数的作用就是通过商品ID查询该商品的描述信息与商品金额

在Controller中调用扫码支付模式一

public class PayController : Controller
{
    private readonly ILazyBasicPayV2 _lazyBasicPay;
    private Random random = new Random();
    public PayController(ILazyBasicPayV2 lazyBasicPay)
    {
    	_lazyBasicPay = lazyBasicPay;
    }
    public FileContentResult GetNativePay1QRCode()
    {
        //商品ID
        var out_trade_no = $"NativePay1-{random.Next(0, 100000)}";
        var code = _lazyBasicPay.GetPrePayQRCode(out_trade_no);
        return new FileContentResult(code, "image/Jpeg");
    }
}

_lazyBasicPay.GetPrePayQRCode(out_trade_no) 该方法接收参数out_trade_no, 生成二维码,该二维码会请求前面中间件中的Func来生成支付信息

代码运行后直接显示二维码

image-20200809203817214

模式二 : GetPayQRCodeAsync

与模式一不同的是, 在传递out_trade_no(可认为是商品ID)的同时又传递了具体的商品信息(body)以及商品金额(total_fee)

参数

  • out_trade_no : 商户订单号, 商户系统内部订单号(商户自定义单号规则即可), 要求32个字符内, 只能是数字、大小写字母_-|* 且在同一个商户号下唯一
  • body : 商品描述
  • total_fee : 总金额(单位 : 分)
  • notify_url : 通知地址, 异步接收微信支付结果通知的回调地址, 通知url必须为外网可访问的url, 不能携带参数, 在LazyWeChat中请使用服务器地址 http://domain.com/LazyWechatListener

在startup中仅需要注册微信支付相关服务,无需添加中间件

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddLazyPay();				//添加微信支付相关服务
}

在Controller中调用扫描支付模式二

public class PayController : Controller
{
    private readonly ILazyBasicPayV2 _lazyBasicPay;
    private Random random = new Random();
    public PayController(ILazyBasicPayV2 lazyBasicPay)
    {
    	_lazyBasicPay = lazyBasicPay;
    }

    public FileContentResult GetNativePay2QRCode()
    {
        //商品ID
        var out_trade_no = $"NativePay2-{random.Next(0, 100000)}";

        //商品描述
        var body = "Body desc for native pay2";

        //商品金额
        var total_fee = 1;

        //支付完毕后通知的URL地址, 请配置为服务器URL地址
        var notify_url = "https://test.lazywechat.cn/LazyWechatListener";

        var code = _lazyBasicPay.GetPayQRCodeAsync(out_trade_no, body, total_fee, notify_url).Result;
        return new FileContentResult(code, "image/Jpeg");
    }
}

Micro(付款码支付)支付

打开微信中的付款码, 收款人员通过扫描设备收款, 但是支付结果可能不会立即返回(例如, 用户设置了免密支付, 该接口可立即得到支付结果, 否则需要调用支付结果查看接口来查看支付结果)

付款码支付 : RunMicroPayAsync

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddLazyPay();				//添加微信支付相关服务
}
  • 参数

    • out_trade_no : 商户订单号, 商户系统内部订单号(商户自定义单号规则即可), 要求32个字符内, 只能是数字、大小写字母_-|* 且在同一个商户号下唯一
    • body : 商品描述
    • total_fee : 总金额(单位 : 分)
    • auth_code : 打开付款码后可以看到二维码上面有"点击可查看付款码数字",该参数定时刷新,且仅可被使用一次
  • 返回值 : XML , 参考 官方文档 https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1

    例如

    <xml>
       <return_code><![CDATA[SUCCESS]]></return_code>
       <return_msg><![CDATA[OK]]></return_msg>
       <appid><![CDATA[wx2421b1c4370ec43b]]></appid>
       <mch_id><![CDATA[10000100]]></mch_id>
       <device_info><![CDATA[1000]]></device_info>
       <nonce_str><![CDATA[GOp3TRyMXzbMlkun]]></nonce_str>
       <sign><![CDATA[D6C76CB785F07992CDE05494BB7DF7FD]]></sign>
       <result_code><![CDATA[SUCCESS]]></result_code>
       <openid><![CDATA[oUpF8uN95-Ptaags6E_roPHg7AG0]]></openid>
       <is_subscribe><![CDATA[Y]]></is_subscribe>
       <trade_type><![CDATA[MICROPAY]]></trade_type>
       <bank_type><![CDATA[CCB_DEBIT]]></bank_type>
       <total_fee>1</total_fee>
       <coupon_fee>0</coupon_fee>
       <fee_type><![CDATA[CNY]]></fee_type>
       <transaction_id><![CDATA[1008450740201411110005820873]]></transaction_id>
       <out_trade_no><![CDATA[1415757673]]></out_trade_no>
       <attach><![CDATA[订单额外描述]]></attach>
       <time_end><![CDATA[20141111170043]]></time_end>
    </xml>
    
public class PayController : Controller
{
    private readonly ILazyBasicPayV2 _lazyBasicPay;
    private Random random = new Random();
    public PayController(ILazyBasicPayV2 lazyBasicPay)
    {
        _lazyBasicPay = lazyBasicPay;
    }

    public async Task<IActionResult> MicroPay()
    {
        //商品ID
        var out_trade_no = $"Micropay-{random.Next(0, 100000)}";

        //商品描述
        var body = "Body desc for Micropay";

        //金额
        var total_fee = 1;

        //付款码数字, 打开付款码后可以看到二维码上面有"点击可查看付款码数字",该参数定时刷新,且仅可被使用一次
        var auth_code = "134927305201477020";

        var returnObject = await _lazyBasicPay.RunMicroPayAsync(out_trade_no, body, total_fee, auth_code);
        ViewBag.returnObject = JsonConvert.SerializeObject(returnObject);
        return View();
    }
}

上面的Action会返回如下结果, 可以看到由于用户未开启免密支付, 所以result_code 返回FAIL

image-20200809213506321

查看支付结果 : OrderQueryAsync

因为付款码支付可能并不会立即返回支付结果, 所以需要调用查看支付结果接口进行查询(此接口也可以查看其它支付方式的支付结果)

  • 参数

    • transaction_id : 微信订单号
    • out_trade_no : 商品ID

    上面两个参数为二选一, 均可以用来查询支付结果, 两者均不为空时, transaction_id 有限选择

  • 返回值

    • dynamic

      {
      	"appid": "wxbb23a029883b991d",
      	"attach": "",
      	"bank_type": "CMB_CREDIT",
      	"cash_fee": "1",
      	"cash_fee_type": "CNY",
      	"fee_type": "CNY",
      	"is_subscribe": "Y",
      	"mch_id": "1501396621",
      	"nonce_str": "2yQBB4lemW1vJR1h",
      	"openid": "oNDiC0d-r7Su5mYCU-mXFSXuhmtQ",
      	"out_trade_no": "Micropay-40151",
      	"result_code": "SUCCESS",
      	"return_code": "SUCCESS",
      	"return_msg": "OK",
      	"sign": "A26AC03640B9D961D7CC1D215F37A6DF",
      	"time_end": "20200815102701",
      	"total_fee": "1",
      	"trade_state": "SUCCESS",
      	"trade_state_desc": "支付成功",
      	"trade_type": "MICROPAY",
      	"transaction_id": "4200000596202008150763626659"
      }
      

    示例代码 :

    public async Task<IActionResult> OrderQuery()
    {
        var transaction_id = "1009660380201506130728806387";
        var out_trade_no = "20150806125346";
        var returnObject = await _lazyBasicPay.OrderQueryAsync(transaction_id, out_trade_no);
        return View();
    }
    

H5支付 : RunH5PayAsync

H5支付是指商户在微信客户端外的移动端网页展示商品或服务,用户在前述页面确认使用微信支付时,商户发起本服务呼起微信客户端进行支付。 主要用于触屏版的手机浏览器请求微信支付的场景。可以方便的从外部浏览器唤起微信支付。

官方文档 : https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_1

H5支付说明

例如, 调用H5支付, 在手机端的非微信浏览器中获取mweb_url, 并跳转到mweb_url, 出现微信支付页面, 点击立即支付来完成h5支付

image-20200810101211431

image-20200810114332393

需要登录商户平台 -> 产品中心 -> 开发配置 -> 支付配置 , 配置H5支付域名

image-20200810113639426

在LazyWeChat中使用H5支付

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddLazyPay();				//添加微信支付相关服务
}

参数说明

public class H5PayModel
{
    /// <summary>
    /// 商户订单号
    /// </summary>
    public string out_trade_no { get; set; }
        
	/// <summary>
    /// 商品描述
    /// </summary>
    public string body { get; set; }

    /// <summary>
    /// 商品价格,单位:分
    /// </summary>
    public int total_fee { get; set; }

    /// <summary>
    /// 通知地址
    /// </summary>
    public string notify_url { get; set; }

    /// <summary>
    /// 设备IP地址
    /// </summary>
    public string spbill_create_ip { get; set; }

    /// <summary>
    /// 场景信息 : 具体请参考后面的统一支付scene_info说明
    /// </summary>
    public dynamic h5_info { get; set; }
}
public async Task<IActionResult> H5Pay()
{
    //下面的field均为H5支付的必填字段
    H5PayModel h5PayModel = new H5PayModel();
    //商品ID
    h5PayModel.out_trade_no = $"H5pay-{random.Next(0, 100000)}";
    //商品描述
    h5PayModel.body = "Body desc for H5Pay";
    //金额
    h5PayModel.total_fee = 1;
    //IP地址
    h5PayModel.spbill_create_ip = "192.168.0.1";
    //支付完毕后通知的URL地址, 请配置为服务器URL地址
    h5PayModel.notify_url = "https://test.lazywechat.cn/LazyWechatListener";
    //场景信息
    h5PayModel.h5_info = new ExpandoObject();
    h5PayModel.h5_info.type = "IOS";
    h5PayModel.h5_info.app_name = "王者荣耀";
    h5PayModel.h5_info.bundle_id = "com.tencent.wzryIOS";

    var returnObject = await _lazyBasicPay.RunH5PayAsync(h5PayModel);

    ViewBag.returnObject = JsonConvert.SerializeObject(returnObject);
    ViewBag.mweb_url = returnObject.mweb_url;
    return View();
}
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>H5Pay</title>
</head>

<body>
    <h3>This is H5pay result checking page</h3>
    <div>
        @ViewBag.returnObject
    </div>
    <div>
        <a href="@ViewBag.mweb_url">mweb_url</a>        
    </div>
</body>
</html>

统一支付scene_info说明

JSAPI

{
	"store_info": {
		"id": "SZTX001",
		"name": "腾大餐厅",
		"area_code": "440305",
		"address": "科技园中一路腾讯大厦"
	}
}

Native

{
	"store_info": {
		"id": "SZTX001",
		"name": "腾大餐厅",
		"area_code": "440305",
		"address": "科技园中一路腾讯大厦"
	}
}

H5
IOS

{
	"h5_info": {
		"type": "IOS",
		"app_name": "王者荣耀",
		"bundle_id": "com.tencent.wzryIOS"
	}
}

Android

{
	"h5_info": {
		"type": "Android",
		"app_name": "王者荣耀",
		"package_name": "com.tencent.tmgp.sgame"
	}
}

WAP

{
	"h5_info": {
		"type": "Wap",
		"wap_url": "https://pay.qq.com",
		"wap_name": "腾讯充值"
	}
}

小程序支付 :RunMiniPayAsync

需要在小程序中申请开通微信支付, 具体步骤请参考如下链接

备注 : 如果小程序有关联的公众号且公众号已经申请了微信支付, 只需关联一下, 否则需要到微信商户平台为小程序申请微信支付

https://pay.weixin.qq.com/static/pay_setting/appid_protocol.shtml

LazyWeChat中使用小程序支付

[HttpGet("MiniPay", Name = "MiniPay")]
public async Task<string> MiniPay()
{
    var out_trade_no = $"MiniPay-{random.Next(0, 100000)}";
    var openid = "ogaiR4iP8a0JHaoCIXMZyFmCAZy4";
    var body = "Body desc for mini";
    var total_fee = 1;
    var spbill_create_ip = "192.168.0.1";
    var notify_url = "https://test.lazywechat.cn/LazyWechatListener";
    var res = await _lazyBasicPay.RunMiniPayAsync(out_trade_no, openid, body, total_fee, spbill_create_ip, notify_url);
    var json = JsonConvert.SerializeObject(res);
    return json;
}

.wxml

<view>
	<button bindtap="bindMiniPay">小程序支付</button>
</view>

.js Onload函数中发送请求, 获取小程序支付参数,并存放在payParam参数中

  • url : LazyWeChat中部署的小程序支付的后台请求地址

  • payParam

    image-20200815103851599

onLoad: function () {
    var that = this
    wx.request({
        url: 'https://test.lazywechat.cn/MiniPay',
        data: {},
        header: { 'content-type': 'application/json' },
        method: 'GET',
        dataType: 'json',
        responseType: 'text',
        success: (result) => {
            that.setData({
                payParam: result.data
            })
        },
        fail: () => { },
        complete: () => { }
    });
}

.js 调用wx.requestPayment函数激发小程序支付

bindMiniPay: function () {
    var that = this.data
    wx.requestPayment({
        timeStamp: that.payParam.timeStamp,
        nonceStr: that.payParam.nonceStr,
        package: that.payParam.package,
        signType: that.payParam.signType,
        paySign: that.payParam.paySign,
        success: (result) => {
        	console.log(result)
        },
        fail: () => {},
        complete: () => {}
    });
}
posted @ 2020-08-26 19:24  LazyWeChat  阅读(829)  评论(1)    收藏  举报