Skip to main content

企业微信向员工收款

介绍

企业微信向员工收款,这是要前端发起支付,像普通的微信H5的JSAPI支付一样。

官方提供的Demo是 PHP混合HTML代码。

我要做的是PHP和HTML分离的方式。

代码实现

1.JS-SDK的接口封装

<?php
/**
* Created by PhpStorm.
* User: 清行
* Date: 2020/3/18
* Time: 17:53
*/

namespace App\Http\Controllers\V1\JSSDK;


use App\Http\Controllers\V1\Controller;
use App\Traits\WorkConfig;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;

class JSSDKController extends Controller
{

use WorkConfig;
protected $tencent_map ;
public function __construct()
{
$this->corpid = config('wework.CORP_ID');
$this->secret = config('wework.CONTACT_SYNC_SECRET');
$url = base_path()."/app/SDK/weworkapi/api/src/CorpAPI.class.php";
$this->token = config('wework.CONTACT_TOKEN');
$this->encodingAESKey = config('wework.CONTACT_EncodingAESKey');
$this->tencent_map = config('app.tencent_map');

include_once (str_ireplace('\\','/',$url));
$this->api = new \CorpAPI($this->corpid, $this->secret);
}

public function getConfig(Request $request)
{
// $url = $request->input('url');

$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";


$data = [
'timestamp' => time(), // 必填,生成签名的时间戳
'nonceStr' => rand(100,200), // 必填,生成签名的随机串
'jsapi_ticket' => $this->get_jsapi_ticket(),
'url' => $url,
];
$data['signature'] = $this->signature($data); // 必填,签名,见 附录-JS-SDK使用权限签名算法

$data['appId'] = $this->corpid; // 必填,企业微信的corpID
$data['jsApiList'] = [
'openLocation',
'getLocation',
'startAutoLBS',
'stopAutoLBS',
'onLocationChange',
'onHistoryBack',
]; // 必填,需要使用的JS接口列表,凡是要调用的接口都需要传进来

return $this->jsonSuccessData($data);
}

//签名,见 附录-JS-SDK使用权限签名算法
public function signature($data)
{
asort($data);
$url = ToUrlParams($data);
return sha1($url);
}


//获取企业的 jsapi_ticket
public function get_jsapi_ticket()
{
$jsapi_ticket = Redis::get('jsapi_ticket:'.$this->corpid);

if (empty($jsapi_ticket))
{
$jsapi_ticket = $this->api->JsApiTicketGet();
Redis::setex('jsapi_ticket:'.$this->corpid,7000,$jsapi_ticket);
}
return $jsapi_ticket;
}

public function getWxConfig(Request $request)
{
$url = $request->input('url');

$jsapiTicket = $this->get_jsapi_ticket();


$timestamp = time();
$nonceStr = createNonceStr();

//这里参数的顺序要按照 key 值 ASCII 码升序排序
$string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";

$signature = sha1($string);

$signPackage = array(
"appId" => $this->corpid,
"nonceStr" => $nonceStr,
"timestamp" => $timestamp,
"url" => $url,
"signature" => $signature,
"rawString" => $string
);

$signPackage['jsApiList'] = [
'openLocation',
'getLocation',
'startAutoLBS',
'stopAutoLBS',
'onLocationChange',
'onHistoryBack',
'getBrandWCPayRequest',
];


return $this->jsonSuccessData($signPackage);
}


}

2. 修改 WxPayConfig.php

下载微信支付官方SDK,封装成psr-4规范,然后修改里面的WxPayConfig配置

<?php
namespace App\Common\Wechat\Wxpay;
use App\Models\WechatMiniPay;
/**
*
* 该类需要业务自己继承, 该类只是作为deamon使用
* 实际部署时,请务必保管自己的商户密钥,证书等
*
*/

class WxPayConfig extends WxPayConfigInterface
{
//=======【基本信息设置】=====================================
/**
* TODO: 修改这里配置为您自己申请的商户信息
* 微信公众号信息配置
*
* APPID:绑定支付的APPID(必须配置,开户邮件中可查看)
*
* MCHID:商户号(必须配置,开户邮件中可查看)
*
*/

private $payInfo;
public $merchant_id;

/**
* WxPayConfig constructor.
* @param $merchant_id 商户ID
*/
public function __construct()
{
$this->merchant_id = config('wework.PAY_MCH_ID');
}

public function GetAppId()
{
return config('wework.CORP_ID');
}
public function GetMerchantId()
{
return $this->merchant_id;
}

//=======【支付相关配置:支付成功回调地址/签名方式】===================================
/**
* TODO:支付回调url
* 签名和验证签名方式, 支持md5和sha256方式
**/
public function GetNotifyUrl()
{
return config('app.api_url').'/api/pay/notify';
}

public function GetSignType()
{
return "HMAC-SHA256";
}

//=======【curl代理设置】===================================
/**
* TODO:这里设置代理机器,只有需要代理的时候才设置,不需要代理,请设置为0.0.0.0和0
* 本例程通过curl使用HTTP POST方法,此处可修改代理服务器,
* 默认CURL_PROXY_HOST=0.0.0.0和CURL_PROXY_PORT=0,此时不开启代理(如有需要才设置)
* @var unknown_type
*/
public function GetProxy(&$proxyHost, &$proxyPort)
{
$proxyHost = "0.0.0.0";
$proxyPort = 0;
}


//=======【上报信息配置】===================================
/**
* TODO:接口调用上报等级,默认紧错误上报(注意:上报超时间为【1s】,上报无论成败【永不抛出异常】,
* 不会影响接口调用流程),开启上报之后,方便微信监控请求调用的质量,建议至少
* 开启错误上报。
* 上报等级,0.关闭上报; 1.仅错误出错上报; 2.全量上报
* @var int
*/
public function GetReportLevenl()
{
return 1;
}


//=======【商户密钥信息-需要业务方继承】===================================
/*
* KEY:商户支付密钥,参考开户邮件设置(必须配置,登录商户平台自行设置), 请妥善保管, 避免密钥泄露
* 设置地址:https://pay.weixin.qq.com/index.php/account/api_cert
*
* APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置, 登录公众平台,进入开发者中心可设置), 请妥善保管, 避免密钥泄露
* 获取地址:https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN
* @var string
*/
public function GetKey()
{
return config('wework.PAY_APP_KEY');
}
public function GetAppSecret()
{
return config('wework.PAY_APP_SECRET');
}


//=======【证书路径设置-需要业务方继承】=====================================
/**
* TODO:设置商户证书路径
* 证书路径,注意应该填写绝对路径(仅退款、撤销订单时需要,可登录商户平台下载,
* API证书下载地址:https://pay.weixin.qq.com/index.php/account/api_cert,下载之前需要安装商户操作证书)
* 注意:
* 1.证书文件不能放在web服务器虚拟目录,应放在有访问权限控制的目录中,防止被他人下载;
* 2.建议将证书文件名改为复杂且不容易猜测的文件名;
* 3.商户服务器要做好病毒和木马防护工作,不被非法侵入者窃取证书文件。
* @var path
*/
public function GetSSLCertPath(&$sslCertPath, &$sslKeyPath)
{
$sslCertPath = storage_path('cert'.DIRECTORY_SEPARATOR.'apiclient_cert.pem');
$sslKeyPath = storage_path('cert'.DIRECTORY_SEPARATOR.'apiclient_key.pem');
}
}

3. 封装预支付单号,和支付成功回调的函数。

我是从我老的项目中拷贝过来,修修改改。

<?php
/**
* Created by PhpStorm.
* User: 清行
* Date: 2019/12/17
* Time: 10:58
*/

namespace App\Http\Controllers\V1\Pay;


use App\Common\Err\ApiErrDesc;
use App\Common\Wechat\Wxpay\WxPayApi;
use App\Common\Wechat\Wxpay\JsApiPay;
use App\Common\Wechat\Wxpay\WxPayUnifiedOrder;
use App\Common\Wechat\Wxpay\WxPayConfig;
use App\Common\Wechat\Wxpay\PayNotifyCallBack;
use App\Exceptions\ApiException;
use App\Http\Controllers\V1\Contacts\UsersController;
use App\Http\Respones\ResponseJson;
use App\Models\GuideBonus;
use App\Models\User;
use Exception;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\Request;

class WechatPayController extends PayNotifyCallBack
{

use ResponseJson;

/**
* 统一下单
*/
public function unifiedOrder(Request $request){
$id = $request->input('id');

if (!empty($id))
{
$bonus_info = GuideBonus::find($id);
if (empty($bonus_info))
{
throw new ApiException(ApiErrDesc::ERR_COUPONS_NONENTITY);
}
}else{
throw new ApiException(ApiErrDesc::ERR_PARAMS);
}

if( $bonus_info['pay_status'] != 2 && $bonus_info['refund_status'] != 1 )
{
throw new ApiException(ApiErrDesc::ERR_PAY_CHECK_ORDER);
}

try{
$user_info = User::where('userid',$bonus_info['userid'])->first();

$user = new UsersController();
$openid = $user->convert_to_openid($bonus_info['userid']);

$config = new WxPayConfig();

//②、统一下单
$input = new WxPayUnifiedOrder();
$input->SetBody( ($bonus_info['type'] = 1) ? '退回销售奖励' : '退回激活奖励' );
$input->SetAttach( "");
$input->SetOut_trade_no($bonus_info['refund_code']);
$input->SetTotal_fee($bonus_info['money']);
$input->SetTime_start(date("YmdHis"));
// $input->SetTime_expire(date("YmdHis", time() + 600));
// $input->SetGoods_tag("test");
$input->SetNotify_url($config->GetNotifyUrl());
$input->SetTrade_type("JSAPI");
$input->SetOpenid($openid);
$input->SetProduct_id("");
$order = WxPayApi::unifiedOrder($config, $input);

$tools = new JsApiPay();
$jsApiParameters = $tools->GetJsApiParameters($order,$user_info['merchant_id']);

return $this->jsonSuccessData(json_decode($jsApiParameters,true));
// //获取共享收货地址js函数参数
// $editAddress = $tools->GetEditAddressParameters();
} catch(Exception $e) {
Log::error(json_encode($e));
throw new ApiException(['400600',$e->getMessage()]);
}
}

/**
*
* 回包前的回调方法
* 业务可以继承该方法,打印日志方便定位
* @param string $xmlData 返回的xml参数
*
**/
public function LogAfterProcess($xmlData)
{
Log::debug("call back, return xml:" . $xmlData);
return;
}

//微信支付回调入口
public function notify()
{
$config = new WxPayConfig();
Log::debug("begin notify");
// $notify = new PayNotifyCallBack();
$this->Handle($config, false);
// $notify->Handle($config, false);
}


/**
* 重写 NotifyProcess
* @param WxPayNotifyResults $objData 回调解释出的参数
* @param WxPayConfigInterface $config
* @param string $msg 如果回调处理失败,可以将错误信息输出到该方法
* @return true回调出来完成不需要继续回调,false回调处理未完成需要继续回调
*/
public function NotifyProcess($objData, $config, &$msg)
{
$data = $objData->GetValues();
//echo "处理回调";
Log::debug("call----back:" . json_encode($data));
//TODO 1、进行参数校验
if(!array_key_exists("return_code", $data)
|| (array_key_exists("return_code", $data) && $data['return_code'] != "SUCCESS")
|| !array_key_exists("openid", $data)
|| !array_key_exists("out_trade_no", $data)
) {
//TODO失败,不是支付成功的通知
//如果有需要可以做失败时候的一些清理处理,并且做一些监控
$msg = "回调数据异常";
Log::debug($msg . json_encode($data));
return false;
}

if(!array_key_exists("transaction_id", $data)){
$msg = "输入参数不正确";
Log::debug($msg . json_encode($data));
return false;
}

//TODO 2、进行签名验证
try {
$checkResult = $objData->CheckSign($config);
if($checkResult == false){
//签名错误
Log::error("签名错误...");
return false;
}
Log::debug("call--back 进行签名验证:" . json_encode($checkResult));
} catch(Exception $e) {
Log::error("签名错误:" .json_encode($e));
}

//TODO 3、处理业务逻辑


if ($data['result_code'] == 'SUCCESS' && $data['result_code'] == 'SUCCESS' )
{
Log::debug("call back 处理业务逻辑:" . json_encode($data));
// 支付成功的逻辑 - 修改奖励单状态,修改订单支付时间
GuideBonus::updateRefundInfo($data['out_trade_no']);
}


//查询订单,判断订单真实性
if(!$this->Queryorder($data["transaction_id"],$config->merchant_id)){
$msg = "订单查询失败";
return false;
}
Log::debug("支付回调成功执行完成");
return true;
}


}

4. 用到的路由

我接口用到了dingo插件。

    //js-sdk
$api->group(['prefix' => 'jssdk','namespace' => 'App\Http\Controllers\V1\JSSDK'], function ($api) {
$api->get('/getConfig', "JSSDKController@getWxConfig");
});

// 企业微信支付
$api->group(['prefix' => 'pay','namespace' => 'App\Http\Controllers\V1\Pay'], function ($api) {
$api->post('/unifiedOrder','WechatPayController@unifiedOrder'); // 统一下单
$api->post('/notify','WechatPayController@notify'); // 回调
});

5. 前端代码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>地图</title>
</head>
<body>
<script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
<script src="jquery-1.9.1.min.js"></script>



<script>

$.get("https://work.tyunai.cn/api/jssdk/getConfig",{url:window.location.href}, function(data){
console.log(data);
var appId = data.data.appId;
var timestamp = data.data.timestamp;
var nonceStr = data.data.nonceStr;
var signature = data.data.signature;
var jsApiList = data.data.jsApiList;


wx.config({
beta: true,// 必须这么写,否则wx.invoke调用形式的jsapi会有问题
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: appId, // 必填,企业微信的corpID
timestamp: timestamp, // 必填,生成签名的时间戳
nonceStr: nonceStr, // 必填,生成签名的随机串
signature: signature,// 必填,签名,见 附录-JS-SDK使用权限签名算法
jsApiList: jsApiList // 必填,需要使用的JS接口列表,凡是要调用的接口都需要传进来
});

});


wx.ready(function () {
//地图
wx.getLocation({
type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
success: function (res) {
console.log(res);
var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
var speed = res.speed; // 速度,以米/每秒计
var accuracy = res.accuracy; // 位置精度
}
});

});


function jsApiCall()
{
$.post("https://xxx.xxx.xxx/api/pay/unifiedOrder",{id:1}, function(data){
console.log(data);
var appId = data.data.appId;
var timestamp = data.data.timeStamp;
var nonceStr = data.data.nonceStr;
var package = data.data.package;
var paySign = data.data.paySign;
var signType = data.data.signType;

WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":appId, //公众号名称,由商户传入
"timeStamp":timestamp, //时间戳,自1970年以来的秒数
"nonceStr":nonceStr, //随机串
"package": package,
"signType":signType, //微信签名方式:
"paySign":paySign //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ){
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
}
);

});
}

function callpay()
{
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', jsApiCall);
document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
}
}else{
jsApiCall();
}
}



</script>

<br>
<br>
<br>
<br>
<div align="center">
<button style="width:210px; height:50px; border-radius: 15px;background-color:#FE6714; border:0px #FE6714 solid; cursor: pointer; color:white; font-size:16px;" type="button" onclick="callpay()" >立即支付</button>
</div>
</body>
</html>

总结

我做的时候问题多出在前端和后端接口联调的地方。

前端可以在“微信开发者工具”里调试,支付的时候,H5页面在企业微信和微信里都能发起支付。

步骤虽多,但总算爬出坑了。希望对大家有帮助。