NET Core Wechat Payment Public Number and H5 Payment Details

Time:2019-9-11

Preface

This paper mainly records the whole process of payment of public number and H5 in Wechat Payment.

Preparation section

Public number or service number (and open the payment function of Wechat), JSAPI payment and H5 payment in the merchant platform.

Configuration section

In the public number or service number – – – Development – – – Developer Tool – – – Web Developer Tool – – – binding as developer

Public Number or Service Number – – – Public Number Settings – – – Functional Settings: Fill in Business Domain Name, JS Security Domain Name, Web Authorized Domain Name Example: pay.one.com

JSAPI Payment Authorization Directory Fill in: http://pay.one.com/http://pay.one.com/WeChatPay/PubPay/- – H5 Payment Fill in: pay.one.com

If you have any questions about the configuration, refer to the official documentation:

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6

Development chapter

JSAPI payment

This Demo is based on Payment SDK development. Details are available at https://github.com/Essensoft/Payment.

First install payment using Nuget:

Install-Package  :Essensoft.AspNetCore.Payment.WeChatPay -Version 2.3.2

Build a Model: WeChatPayPubPayViewModel


public class WeChatPayPubPayViewModel
  {
    [Required]
    [Display(Name = "out_trade_no")]
    public string OutTradeNo { get; set; }

    [Required]
    [Display(Name = "body")]
    public string Body { get; set; }

    [Required]
    [Display(Name = "total_fee")]
    public int TotalFee { get; set; }

    [Required]
    [Display(Name = "spbill_create_ip")]
    public string SpbillCreateIp { get; set; }

    [Required]
    [Display(Name = "notify_url")]
    public string NotifyUrl { get; set; }

    [Required]
    [Display(Name = "trade_type")]
    public string TradeType { get; set; }

    [Required]
    [Display(Name = "openid")]
    public string OpenId { get; set; }
  }

WeChatPayController:

// Wechat Payment Request Client (for processing requests and responses)
private readonly IWeChatPayClient _client;
private readonly ILogger<WeChatPayController> _logger;

 private IHttpContextAccessor _accessor;

public WeChatPayController(IWeChatPayClient client, IHttpContextAccessor accessor, ILogger<WeChatPayController> logger)
    {
      _client = client;
      _accessor = accessor;
      _logger = logger;
    }
    /// <summary>
    /// Public Number Payment
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    public IActionResult PubPay()
    {
      WeChatPayPubPayViewModel payModel=new WeChatPayPubPayViewModel()
      {
        Body = Wechat Public Number Payment Test
        OutTradeNo = DateTime.Now.ToString("yyyyMMddHHmmssfff"),
        TotalFee = 1, // Subunit
        SpbillCreateIp = "127.0.0.1",
        NotifyUrl = "http://pay.one.com/notify/wechatpay/unifiedorder",
        TradeType = "JSAPI",
        OpenId = ""// Authorization is required here to obtain OpenId
      };
      return View(payModel);
    }

    /// <summary>
    /// Public Number Payment
    /// </summary>
    /// <param name="viewModel"></param>
    /// <returns></returns>
    [HttpPost]
    public async Task<IActionResult> PubPay(WeChatPayPubPayViewModel viewModel)
    {
      if(string.IsNullOrEmpty(viewModel.OpenId))
      {
        ViewData ["response"]= "Please go back to your superior and re-enter this page to get the latest data";
        return View();
      }

      var request = new WeChatPayUnifiedOrderRequest
      {
        Body = viewModel.Body,
        OutTradeNo = viewModel.OutTradeNo,
        TotalFee = viewModel.TotalFee,
        SpbillCreateIp = viewModel.SpbillCreateIp,
        NotifyUrl = viewModel.NotifyUrl,
        TradeType = viewModel.TradeType,
        OpenId = viewModel. OpenId /// Authorization is required to obtain OpenId here
      };
      var response = await _client.ExecuteAsync(request);if (response.ReturnCode == "SUCCESS" && response.ResultCode == "SUCCESS")
      {
        var req = new WeChatPayH5CallPaymentRequest
        {
          Package = "prepay_id=" + response.PrepayId
        };
        var parameter = await _client.ExecuteAsync(req);
        // Give the parameter to the front end of the public number so that he can start paying in the Wechat H5 (https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?Chapter=7_7&index=6)
        ViewData["parameter"] = JsonConvert.SerializeObject(parameter);
        ViewData["response"] = response.Body;
        return View();
      }
      ViewData["response"] = response.Body;
      return View();
    }

Note: Payments in public numbers or Wechat need to be authorized to obtain the user’s OpenId. Therefore, we also need to authorize Wechat here. There are two ways of authorization, one is silent authorization, the other is user consent. The difference is that silent authorization can only get Openid, and other information such as Weichat avatar, nickname, gender can be obtained with user consent.

See the documentation: https://mp.weixin.qq.com/wiki?T=resource/res_main&id=mp1421140842.

Page:

@using Newtonsoft.Json
@model WeChatPayPubPayViewModel
@{
  ViewData ["Title"]= "Public Number Payment - Uniform Order Placement";
}
<nav aria-label="breadcrumb">
  <ol>
    <li> <a asp-controller= "WeChatPay" asp-action= "Index">Wechat Payment</a></li>
    <li aria-current="page">@ViewData["Title"]</li>
  </ol>
</nav>
<br />
<div>
  <div>
    <form asp-controller="WeChatPay" asp-action="PubPay">
      <div asp-validation-summary="All"></div>
      <div>
        <label asp-for="OutTradeNo"></label>
        <input type="text" asp-for="OutTradeNo" value="@Model?.OutTradeNo" />
      </div>
      <div>
        <label asp-for="Body"></label>
        <input type="text" asp-for="Body" value="@Model?.Body" />
      </div>
      <div>
        <label asp-for="TotalFee"></label>
        <input type="text" asp-for="TotalFee" value="@Model?.TotalFee" />
      </div>
      <div>
        <label asp-for="SpbillCreateIp"></label>
        <input type="text" asp-for="SpbillCreateIp" value="@Model?.SpbillCreateIp" />
      </div>
      <div>
        <label asp-for="NotifyUrl"></label>
        <input type="text" asp-for="NotifyUrl" value="@Model?.NotifyUrl" />
      </div>
      <div>
        <label asp-for="TradeType"></label>
        <input type="text" asp-for="TradeType" value="@Model?.TradeType" />
      </div>
      <div>
        <label asp-for="OpenId"></label>
        <input type="text" asp-for="OpenId" value="@Model?.OpenId" />
      </div>
      <button type="submit">submit request </button>
      <button type="button">Pay immediately </button>
    </form>
    <hr />
    <form>
      <div>
        <label>Response:</label>
        <textarea rows="10">@ViewData["response"]</textarea>
      </div>
      <div>
        <label>Parameter:</label>
        <textarea rows="3">@ViewData["parameter"]</textarea>
      </div>
    </form>
  </div>
</div>
@section Scripts {
  @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script type="text/javascript">
  $(function () {
    $("#PayNow").on('click', function () {
      const local = "http://pay.one.com/WeChatPay/PayBack/"; 
       window.location.href ='https://open.weixin.qq.com/connect/oauth2/[email protected]&redirect_uri=' + encodeURIComponent(local)+'&response_type=code&scope=snsapi_base&state=a#wechat_redirect';
    });
  
  });

</script>

At this point: PayBack Action is as follows:

[HttpGet]
    public async Task<IActionResult> PayBack()
    {
      var code = Request.Query["code"];
      var state = Request.Query["state"];
      OAuthToken tokenModel = new OAuthToken();
      // Exchange code for token
      if (!string.IsNullOrEmpty(code))
      {
        _ Logger. LogWarning ("Authorization Successful");
        ViewBag.Code = code;
        tokenModel = OauthApi.GetAuthToken(code, wechatAppId);
      }

      var request = new WeChatPayUnifiedOrderRequest
      {
        Body = Wechat Public Number Payment Test
        OutTradeNo = DateTime.Now.ToString("yyyyMMddHHmmssfff"),
        TotalFee = 1, // Subunit
        SpbillCreateIp = "127.0.0.1",
        NotifyUrl = "http://pay.one.com/notify/wechatpay/unifiedorder",
        TradeType = "JSAPI",
        OpenId = tokenModel. Openid /// Authorization is required to obtain OpenId here
      };
      var response = await _client.ExecuteAsync(request);
      _ Logger. LogWarning ($Unified Single Interface Return: {response. ReturnCode});

      if (response.ReturnCode == "SUCCESS" && response.ResultCode == "SUCCESS")
      {
        var req = new WeChatPayH5CallPaymentRequest
        {
          Package = "prepay_id=" + response.PrepayId
        };
        var parameter = await _client.ExecuteAsync(req);
        // Give the parameter to the front end of the public number so that he can start paying in the Wechat H5 (https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?Chapter=7_7&index=6)
        ViewData["parameter"] = JsonConvert.SerializeObject(parameter);
        _ Logger. LogWarning ($"Unified order succeeded, Wechat payment is about to be raised: {ViewData [" parameter "]. ToString ()});
        ViewData["response"] = response.Body;
        return View();
      }
      ViewData["response"] = response.Body;

      
      return View();
    }

Among them: OAuthToken is the entity returned by web authorization:

/// Entities returned when obtaining the page authorization token
  /// </summary>
  public class OAuthToken : BaseRes
  {
    /// <summary>
    /// Web authorization interface calls credentials. Note: This access_token is different from the underlying access_token
    /// </summary>
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }
    private int _expiresIn;
    /// <summary>
    /// Access_token interface calls credentials timeout in seconds
    /// </summary>
    [JsonProperty("expires_in")]
    public int ExpiresIn
    {
      get { return _expiresIn; }
      set
      {
        ExpiresTime = DateTime.Now.AddSeconds(value);
        _expiresIn = value;
      }
    }
    /// <summary>
    /// Used to refresh access_token
    /// </summary>
    [JsonProperty("refresh_token")]
    public string RefreshToken { get; set; }
    /// <summary>
    /// User Unique Identification. Note that when a user visits a public number page without paying attention to the public number, a unique openid for the user and the public number will also be generated.
    /// </summary>
    [JsonProperty("openid")]
    public string Openid { get; set; }
    /// <summary>
    /// User authorized scopes, separated by commas (,)
    /// </summary>
    [JsonProperty("scope")]
    public string Scope { get; set; }
    [JsonProperty("expires_time")]
    public DateTime ExpiresTime { get; set; }
    /// <summary>
    /// This field will only appear if the user binds the public number to the Wechat Open Platform account.
    /// </summary>
    [JsonProperty("unionid")]
    public string Unionid { get; set; }
  }

Finally, the callback function after successful payment is pasted:

[Route("notify/wechatpay")]
  public class WeChatPayNotifyController : Controller
  {
    private readonly IWeChatPayNotifyClient _client;
    private readonly ILogger<WeChatPayNotifyController> _logger;
    public WeChatPayNotifyController(IWeChatPayNotifyClient client,ILogger<WeChatPayNotifyController> logger)
    {
      _client = client;
      _logger = logger;
    }

    /// <summary>
    /// Uniform Order Payment Result Notification
    /// </summary>
    /// <returns></returns>
    [Route("unifiedorder")]
    [HttpPost]
    public async Task<IActionResult> Unifiedorder()
    {
      try
      {
        _ Logger. LogWarning ($Enter Callback);
        var payconfig = OpenApi.GetPayConfig();
        var notify = await _client.ExecuteAsync<WeChatPayUnifiedOrderNotify>(Request);
        _ Logger. LogWarning ($"Return status code: {notify. Return Code}");

        if (notify.ReturnCode == "SUCCESS")
        {
          _ Logger. LogWarning ($"Business Result Code: {notify. ResultCode}");

          if (notify.ResultCode == "SUCCESS")
          {
            _ Logger. LogWarning ($"Payment method: {notify. TradeType}";
            _ Logger. LogWarning ($"Merchant Order Number: {notify. OutTradeNo});
            _ Logger. LogWarning ($"Wechat Payment Order Number: {notify. Transaction Id}");
            _ Logger. LogWarning ($"Payment: {notify. TotalFee});
            return WeChatPayNotifyResult.Success;
          }
        }
        return NoContent();
      }
      catch(Exception ex)
      {
        _ Logger. LogWarning ($"Callback Failure: {ex. Message});
        return NoContent();
      }
    }
}

Then test the payment and check the server logs as follows:

H5 payment

H5 payment refers to Wechat reply operation on mobile browser except Wechat browser.

Consistent with the above steps, there are several points to be noted

1: Client IP problem: When H5 pays, the Wechat payment system will pay Ip according to the current Ip raised by the client. If it finds that there is a problem with the IP when the payment request is initiated, the payment will fail or the system will be prompted to be busy. Here’s my code for getting IP:

Utils. GetUserIp (_accessor. HttpContext); // page call


    /// <summary>
    /// Acquiring Real IP Through Proxy Server
    /// </summary>
    /// <returns></returns>
    public static string GetUserIp(this HttpContext context)
    {
      var ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
      if (string.IsNullOrEmpty(ip))
      {
        ip = context.Connection.RemoteIpAddress.ToString();
      }
      return ip;
      
    }

2: The TradeType type should be: MWEB

3: If the Wechat payment is successful, the default callback is to the payment home page. If the callback page needs to be set, it can be spliced in the URl:

/// <summary>
    // / H5 payment
    /// </summary>
    /// <param name="viewModel"></param>
    /// <returns></returns>
    [HttpPost]
    public async Task<IActionResult> H5Pay(WeChatPayH5PayViewModel viewModel)
    {
      var request = new WeChatPayUnifiedOrderRequest
      {
        Body = viewModel.Body,
        OutTradeNo = viewModel.OutTradeNo,
        TotalFee = viewModel.TotalFee,
        SpbillCreateIp = viewModel.SpbillCreateIp,
        NotifyUrl = viewModel.NotifyUrl,
        TradeType = viewModel.TradeType
      };
      var response = await _client.ExecuteAsync(request);

      // mweb_url is the middle page of the Wechat Payment Cashier. It can be accessed to pull up the Wechat client and complete the payment. The validity of mweb_url is 5 minutes.
      if (response.MwebUrl == null)
      {
        ViewData["response"] = response.ReturnMsg;
        return View();
      }
      return Redirect(response.MwebUrl);
    }

More detailed documentation available: https://pay.weixin.qq.com/wiki/doc/api/H5.php?Chapter=15_4

4: Notification of Payment Result:

Be careful:

1. The same notification may be sent to the merchant system many times. Business systems must be able to handle duplicate notifications correctly.

2. In the background notification interaction, if the reply from the merchant does not conform to the specifications or time-outs, Wechat will decide that the notification failed and send the notification again until it succeeded. (In the case of unsuccessful notification, Wechat will issue 10 notifications, the frequency of notification is 15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h. / 3h/6h/6h – Total 24h 4m, but Wechat does not guarantee that the notification will eventually succeed.

3. In the case that the order status is unknown or no notification of the result of Wechat payment has been received, it is suggested that merchants actively call Wechat Payment [Inquiry Order API] to confirm the order status.

Special reminder:

1. The content of payment result notification must be done by the merchant system.Signature verification and check whether the returned order amount is consistent with the order amount on the merchant sideTo prevent data leakage from causing “false notification” and causing financial losses.

2. When receiving a notification for processing, first check the status of the corresponding business data, determine whether the notification has been processed, if not processed before processing, if the processing directly returns the result successfully. Before checking and processing the status of business data, data lock should be used for concurrency control to avoid data confusion caused by function reentry.

Finally, you can test the H5 payment and see the logs returned:

The above is the whole content of this article. I hope it will be helpful to everyone’s study, and I hope you will support developpaer more.

Recommended Today

Hadoop MapReduce Spark Configuration Item

Scope of application The configuration items covered in this article are mainly for Hadoop 2.x and Spark 2.x. MapReduce Official documents https://hadoop.apache.org/doc…Lower left corner: mapred-default.xml Examples of configuration items name value description mapreduce.job.reduce.slowstart.completedmaps 0.05 Resource requests for Reduce Task will not be made until the percentage of Map Task completed reaches that value. mapreduce.output.fileoutputformat.compress false […]