最近项目业务需要用到ISV服务商模式,然后我也去做了一些了解,然后就开始准备调整原有的支付功能,迁移到isv模式。ISV模式大概的描述就不多说了,支付宝的文档还是挺详细的,在这中间遇到了一些问题,在这里记录一下。

APP支付

项目早期就已经集成了APP支付,我们在后端将订单信息进行一系列处理、签名之后,将信息通过接口返回给前端,前端直接使用这些信息去调用支付宝的SDK,这样的话前端不需要储存任何敏感信息。

 public static string GetOrderInfoIsv(string title, string contents, string outTradeNo, string strMoney, string notifyUrl)
        {
            //公共参数部分
            var payInfo = new Dictionary<string, string>
            {
                {"app_id", Config.AppIdSandBox},
                {"biz_content", GetBizContentIsv(title, contents, outTradeNo, strMoney, "")},
                {"charset", "utf-8"},
                {"format", "json"},
                {"method", "alipay.trade.app.pay"},
                {"notify_url", notifyUrl},
                {"sign_type", "RSA2"},
                {"timestamp", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")},
                {"version", "1.0"}
            };
            var strUrl = BuildQueryWithOutEncode(payInfo);
            var sign = AlipaySignature.RSASign(strUrl, Config.PrivateKeySandBox, Config.Input_charset, Config.Sign_type, false);
            payInfo.Add("sign", sign);
            strUrl = GetOrderInfoWithEncode(payInfo);
            strUrl = strUrl.Replace("+", "%20");//日期那里会有一个空格(2017-01-05 11:11:11)转化为+,所以这里要替换一下
            return strUrl;
        }

这里是原来的核心拼接逻辑,后端将得到的结果(也就是 strUrl 的内容)通过接口返回给前端,前端直接去调起支付。这个代码是没有问题的,但是在isv模式下,需要传入一个授权token,查阅文档后发现对应的参数是 app_auth_token ,然后将获取得token传入进去:

 public static string GetOrderInfoIsv(string title, string contents, string outTradeNo, string strMoney, string notifyUrl)
        {
            var payInfo = new Dictionary<string, string>
            {
                {"app_id", Config.AppIdSandBox},
                {"app_auth_token", "你的授权token"},
                {"biz_content", GetBizContentIsv(title, contents, outTradeNo, strMoney, "")},
                {"charset", "utf-8"},
                {"format", "json"},
                {"method", "alipay.trade.app.pay"},
                {"notify_url", notifyUrl},
                {"sign_type", "RSA2"},
                {"timestamp", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")},
                {"version", "1.0"}
            };
            var strUrl = BuildQueryWithOutEncode(payInfo);
            var sign = AlipaySignature.RSASign(strUrl, Config.PrivateKeySandBox, Config.Input_charset, Config.Sign_type, false);
            payInfo.Add("sign", sign);
            strUrl = GetOrderInfoWithEncode(payInfo);
            strUrl = strUrl.Replace("+", "%20");//日期那里会有一个空格(2017-01-05 11:11:11)转化为+,所以这里要替换一下
            return strUrl;
        }

添加了app_auth_token参数之后,如果你的token不正确是会弹出错误提示的,会提示你错误的授权令牌,你需要重新授权一下。

注意:如果你使用沙箱环境进行测试的话,一定要使用卖家账号进行授权才行。

然而在更换了正确的token之后,还是不能成功拉起支付,会报ALIN10146错误。通过使用支付宝开放平台工具,发现是签名不对应的问题,但是我又想不到有啥不对的,不加token就没问题,加了token就不行,脑壳疼。

最终解决方案

最后我放弃了我们这种有点古老的拼接方式,改成使用支付宝提供的 .net 服务端的SDK,这样我就不用去管签名的问题了,以下是Demo代码:

 [HttpGet]
        public object AppPay()
        {
            try
            {
                IAopClient client =new DefaultAopClient(AliPayConfig.AliDomainSandBox,
                AliPayConfig.AppIdSandBox, AliPayConfig.PrivateKeySandBox, "json", "1.0", "RSA2",
                AliPayConfig.PublicKeySandBox, "utf-8", false));
                var param = new AppPayRequest
                {
                    AccessParams = new AccessParams(),
                    ExtendParams = new ExtendParams()
                    {
                        StoreId = "100086",
                        SysServiceProviderId = "填写 账户中心-合作伙伴管理页面的 PID"
                    },
                    OutTradeNo = DateTime.Now.ToString("yyyyMMddHHmmssfff"),
                    Subject = "APP支付ISV",
                    TotalAmount = "100",
                    ProductCode = "QUICK_MSECURITY_PAY",
                    Body = "APP支付ISV",
                    TimeoutExpress = "30m"
                };
                var request = new AlipayTradeAppPayRequest();
                request.SetNeedEncrypt(false);
                request.SetNotifyUrl("支付回调在这里进行设置");
                request.BizContent = JsonConvert.SerializeObject(param);

                var response = client.SdkExecute(request, AliPayConfig.AppAuthTokenSandBox); 
                 //注意这里调用的是 SdkExecute 方法,而非 Execute 方法,如果使用 Execute 方法会返回404
                return response;
            }
            catch (Exception e)
            {
                return e;
            }
        }



    //请求参数实体
  public class AppPayRequest
    {
        [JsonProperty("total_amount")]
        public string TotalAmount { get; set; }

        [JsonProperty("subject")]
        public string Subject { get; set; }

        [JsonProperty("out_trade_no")]
        public string OutTradeNo { get; set; }

        [JsonProperty("goods_type")]
        public string GoodsType => "1";

        [JsonProperty("extend_params")]
        public ExtendParams ExtendParams { get; set; }

        [JsonProperty("access_params")]
        public AccessParams AccessParams { get; set; }

        public string ProductCode { get; set; }
        public string Body { get; set; }
        public string TimeoutExpress { get; set; }
        public string SellerId { get; set; }
    }

    public class ExtendParams
    {
        [JsonProperty("sys_service_provider_id")]
        public string SysServiceProviderId { get; set; }

        [JsonProperty("store_id")]
        public string StoreId { get; set; }
    }

    public class AccessParams
    {
        [JsonProperty("channel")]
        public string Channel => "ALIPAYAPP";
    }

手机网站支付

在APP支付使用SDK之后,手机网站支付就变得非常简单了,以下是demo代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using AliPayISV.Entities;
using Aop.Api;
using Aop.Api.Request;
using Newtonsoft.Json;

namespace AliPayISV.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home
        public ActionResult Index()
        {
            try
            {
                IAopClient client =new DefaultAopClient(AliPayConfig.AliDomainSandBox,
                AliPayConfig.AppIdSandBox, AliPayConfig.PrivateKeySandBox, "json", "1.0", "RSA2",
                AliPayConfig.PublicKeySandBox, "utf-8", false));
                var param = new AppPayRequest
                {
                    AccessParams = new AccessParams(),
                    ExtendParams = new ExtendParams()
                    {
                        StoreId = "100086",
                        SysServiceProviderId = "服务商的PID"
                    },
                    OutTradeNo = DateTime.Now.ToString("yyyyMMddHHmmssfff"),
                    Subject = "APP支付ISV",
                    TotalAmount = "100",
                    ProductCode = "QUICK_MSECURITY_PAY",
                    Body = "APP支付ISV",
                    TimeoutExpress = "30m"
                };
                var request = new AlipayTradeWapPayRequest();
                request.SetReturnUrl("返回的页面");
                request.SetNotifyUrl("回调页面");
                request.BizContent = JsonConvert.SerializeObject(param);

                var response = client.pageExecute(request, null, AliPayConfig.AppAuthTokenSandBox);
                //注意这里调用的是 pageExecute 方法
                return Content(response.Body);
            }
            catch (Exception e)
            {
                return Content(e.Message);
            }

        }
    }
}

当面付

当面付就更简单了,这个当初也是我开发的,所以一直使用的都是SDK,demo代码如下:

 [HttpGet]
        public object AliF2FPay()
        {
            try
            {
                var client = new DefaultAopClient(AliPayConfig.AliDomainSandBox,
                AliPayConfig.AppIdSandBox, AliPayConfig.PrivateKeySandBox, "json", "1.0", "RSA2",
                AliPayConfig.PublicKeySandBox, "utf-8", false));
                var request = new AlipayTradePrecreateRequest();
                var param = new AppPayRequest()
                {
                    OutTradeNo = DateTime.Now.ToString("yyyyMMddHHmmssfff"),
                    Subject = "当面付测试",
                    TotalAmount = "1000",
                    ExtendParams = new ExtendParams()
                    {
                        StoreId = "10086",
                        SysServiceProviderId = "服务商的pid"  //合作伙伴中的pid
                    }
                };
                request.SetNotifyUrl(AliPayConfig.AuthNotifyLink); 
                request.BizContent = JsonConvert.SerializeObject(param);
                var response = client.Execute(request, null, AliPayConfig.AppAuthTokenSandBox);
                return response;
            }
            catch (Exception e)
            {
                return e;
            }
        }

关键参数释疑

  • sys_service_provider_id 填写服务商的PID,PID在下图位置

20200509101253

结语

文中所有的配置都是使用的沙箱环境进行的测试,也都已经调试通过了,由于子商户还没有申请下来,所以还没有正式的线上测试,不过想来问题应该不大,还有一些注意事项在支付宝的接入指南中已经说得很详细了,也就不再赘述了,放几个接入指南链接吧。如果读者(我感觉没人会读)接入时遇到什么问题,欢迎留言探讨。
* 支付宝ISV入驻以及返佣必读
* 第三方应用授权


遇到你之后,我才知道,原来这世间是如此的美好。