一、前言
首先去微信支付服务商平台查看相关接口和官方提供的sdk。网址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay6_0.shtml,了解清楚支付需要的参数和需要申请的微信服务商相关信息。
二、开始demo
- 我使用的是php语言,用的thinkphp框架,所以使用composer直接拉去SDK就可以。
composer require wechatpay/wechatpay - 按照sdk的约定去堆积代码块,做好参数验证就可以了。
本类库是以 `OpenAPI` 对应的接入点 `URL.pathname` 以`/`做切分,映射成`segments`<sup>[RFC3986](
1. 请求 `pathname` 切分后的每个`segment`,可直接以对象获取形式串接,例如 `v3/pay/transactions/native` 即串成 `v3->pay->transactions->native`;
2. 每个 `pathname` 所支持的 `HTTP METHOD`,即作为被串接对象的末尾执行方法,例如: `v3->pay->transactions->native->post(['json' => []])`;
3. 每个 `pathname` 所支持的 `HTTP METHOD`,同时支持`Async`语法糖,例如: `v3->pay->transactions->native->postAsync(['json' => []])`;
4. 每个 `segment` 有中线(dash)分隔符的,可以使用驼峰`camelCase`风格书写,例如: `merchant-service`可写成 `merchantService`,或如 `{'merchant-service'}`;
5. 每个 `segment` 中,若有`uri_template`动态参数<sup>[RFC6570](
6. SDK内置以 `v2` 特殊标识为 `APIv2` 的起始 `segmemt`,之后串接切分后的 `segments`,如源 `pay/micropay` 即串成 `v2->pay->micropay->post(['xml' => []])` 即以XML形式请求远端接口;
7. 在IDE集成环境下,也可以按照内置的`chain($segment)`接口规范,直接以`pathname`作为变量`$segment`,来获取`OpenAPI`接入点的`endpoints`串接对象,驱动末尾执行方法(填入对应参数),发起请求,例如 `chain('v3/pay/transactions/jsapi')->post(['json' => []])`;
以下示例用法,以`异步(Async/PromiseA+)`或`同步(Sync)`结合此种编码模式展开。
3.一个简单的聚合支付例子。
<?php
namespace app\v2pay\model;
use think\Model;
use think\Db;
use WeChatPay\Builder;
use WeChatPay\Util\PemUtil;
class Wechat Extends Model{
protected $instance = '';
protected $result = ['code'=>3,'msg'=>"内部请求错误!",'total'=>0,'ret_data'=>[],'response_data'=>''];
public function __construct()
{
$merchantId = '你的服务商户号';
$merchantPrivateKeyFilePath = '你的商户私钥路径';
$merchantPrivateKeyInstance = PemUtil::loadPrivateKey($merchantPrivateKeyFilePath);
$merchantCertificateSerial = '商户证书序列号';
$platformCertificateFilePath = '证书保存路径';
$platformCertificateInstance = PemUtil::loadCertificate($platformCertificateFilePath);
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateInstance);
$this->instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformCertificateInstance,
],
]);
}
public function pay($sub_mchid,$orderId,$amount,$expireTime,$goodsName,$notifyUrl,$payType,$appId,$openId,$attach)
{
if ($orderId && $sub_mchid && $amount && $notifyUrl && $goodsName) {
$Redis = new \Redis();
$Redis->connect(config('redis')['host'], config('redis')['port']);
$Redis->auth(config('redis')['auth']);
$Redis->select(1);
$res = $Redis->hExists('wechat_' . $orderId, 'payCode');
if ($res) {
$list = $Redis->hGet('wechat_' . $orderId, 'payCode');
$this->result['code'] = 0;
$this->result['msg'] = "成功";
$this->result['ret_data'] = json_decode($list, true);
return $this->result;
}
if($payType== '1'){
$pay_type = 'native';
}elseif ($payType == '2'){
$pay_type = 'app';
}elseif ($payType == '3' || $payType == '4' ){
$pay_type = 'jsapi';
}else{
$pay_type = 'h5';
}
try {
$params = [
'sp_appid' => $appId,
'sp_mchid' => config('sp_mchid'),
'out_trade_no' => $orderId,
'sub_appid' => $appId,
'sub_mchid' => $sub_mchid,
'description' => $goodsName,
'notify_url' => $notifyUrl,
'amount' => [
'total' => $amount * 100,
'currency' => 'CNY'
],
'settle_info' => [
'profit_sharing' => false
]
];
if(!empty($attach)){
$params['attach'] = $attach;
}
if($pay_type == 'jsapi'){
$params['payer']['sub_openid'] = $openId;
$params['scene_info']['payer_client_ip'] = self::get_realIp();
}elseif ($pay_type == 'h5'){
$params['scene_info']['payer_client_ip'] = self::get_realIp();
$params['scene_info']['h5_info']['type'] = 'Wap';
}
$resp = $this->instance->v3->pay->partner->transactions->$pay_type->post(['json' => $params]);
if ($resp->getStatusCode() == 200 && $resp->getReasonPhrase() == 'OK') {
$result = [];
$payCode = [];
$re = json_decode($resp->getBody(), true);
if ($pay_type == 'jsapi' && isset($re['prepay_id'])) {
$payCode['appId'] = $appId;
$payCode['timeStamp'] = (string)time();
$payCode['nonceStr'] = uniqid();
$payCode['package'] = 'prepay_id=' . $re['prepay_id'];
$payCode['signType'] = 'RSA';
$payCode['paySign'] = self::getPaySign($payCode);
} elseif ($pay_type == 'native' && isset($re['code_url'])) {
$payCode = $re['code_url'];
} elseif ($pay_type == 'h5' && isset($re['h5_url'])) {
$payCode = $re['h5_url'];
}elseif ($pay_type == 'app' && isset($re['prepay_id'])){
$payCode['appId'] = $appId;
$payCode['timeStamp'] = (string)time();
$payCode['nonceStr'] = uniqid();
$payCode['package'] = 'prepay_id=' . $re['prepay_id'];
$payCode['signType'] = 'RSA';
$payCode['paySign'] = self::getPaySign($payCode);
}
$result['orderId'] = $orderId;
$result['uniqueOrderNo'] = '';
$result['prePayTn'] = $payCode;
$log = [
'order_id' => $orderId,
'post_data' => json_encode($params, JSON_UNESCAPED_UNICODE),
'response_data' => json_encode($result, JSON_UNESCAPED_UNICODE),
'create_time' => date('Y-m-d H:i:s'),
];
Db::table('pay_log')->insert($log);
$Redis->hSet('wechat_'.$orderId,'payCode',json_encode($result,JSON_UNESCAPED_UNICODE));
if (!empty($expireTime)) {
$ttl = strtotime($expireTime) - time() - 10;
$Redis->expire('wechat_' . $orderId, $ttl);
} else {
$Redis->expire('wechat_' . $orderId, 7100);
}
$this->result['code'] = 0;
$this->result['msg'] = '成功';
$this->result['ret_data'] = $result;
}
} catch (\Exception $e) {
$this->result['msg'] = $e->getMessage();
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
$this->result['msg'] = json_decode($r->getBody()->getContents(), true)['message'];
}
}
} else {
$this->result['msg'] = "参数有误!";
}
return $this->result;
}
public static function getPaySign($result)
{
$merchantPrivateKeyFilePath = '你的商户私钥路径';
$private_key = PemUtil::loadPrivateKey($merchantPrivateKeyFilePath);
$message = $result['appId'] . "\n" .
$result['timeStamp'] . "\n" .
$result['nonceStr'] . "\n" .
$result['package'] . "\n";
openssl_sign($message, $raw_sign, $private_key, 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);
return $sign;
}
public static function get_realIp()
{
if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
$ip = getenv('HTTP_CLIENT_IP');
} elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
$ip = getenv('HTTP_X_FORWARDED_FOR');
} elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
$ip = getenv('REMOTE_ADDR');
} elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
$ip = $_SERVER['REMOTE_ADDR'];
}
$res = preg_match ( '/[\d\.]{7,15}/', $ip, $matches ) ? $matches [0] : '';
return $res;
}
}
public function queryOrder($orderId){
if($orderId){
$record = Db::table('pay_record')->where("order_id = $orderId ")->find();
if(empty($record)){
$this->result['msg'] = '未找到该订单';
return $this->result;
}
$sub_mchid = $record['mch_app_id'];
$res = $this->instance->v3->pay->partner->transactions->outTradeNo->{'{out_trade_no}'}
->getAsync([
'query' => ['sp_mchid'=>config('sp_mchid'), 'sub_mchid'=>"$sub_mchid"],
'out_trade_no' => $orderId,
])
->then(static function($response) {
$data = [];
if($response->getStatusCode() == 200 && $response->getReasonPhrase() == 'OK'){
$data['code'] = 0;
$data['msg'] = '成功';
$data['list'] = json_decode($response->getBody()->getContents(),true);
return $data;
}
})->otherwise(static function($e) {
$data = [];
$data['msg'] = '接口异常,请稍后重试';
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
$data['msg'] = json_decode($r->getBody()->getContents(),true)['message'];
}
return $data;
})->wait();
if(!empty($res) && isset($res['list'])){
$this->result['ret_data'] = $res;
}else{
$this->result['msg'] = $res['msg'];
}
}else {
$this->result['msg'] = "orderId不能为空!";
}
return $this->result;
}
public function refund($orderId,$refundId,$refundAmount,$sub_mchid,$total,$notifyUrl){
$res = $this->instance->chain('v3/refund/domestic/refunds')
->postAsync([
'json' => [
'sub_mchid'=> strval($sub_mchid),
'out_trade_no' => $orderId,
'out_refund_no' => $refundId,
'notify_url' => $notifyUrl,
'amount' => [
'refund' => $refundAmount*100,
'total' => $total*100,
'currency' => 'CNY',
],
],
])->then(static function($response) {
$data = [];
if($response->getStatusCode() == 200 && $response->getReasonPhrase() == 'OK'){
$data['code'] = 0;
$data['msg'] = '成功';
$data['list'] = json_decode($response->getBody()->getContents(),true);
return $data;
}
})->otherwise(static function($e) {
$data = [];
$data['msg'] = '接口异常,请稍后重试';
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
$data['msg'] = json_decode($r->getBody()->getContents(),true)['message'];
return $data;
}
})->wait();
if(!empty($res) && isset($res['list'])){
$this->result['ret_data'] = $res;
}else{
$this->result['msg'] = $res['msg'];
}
return $this->result;
}
public function wechatBack()
{
switch (strtolower($this->request->method())){
case "post":
$statuscode = 500;
$platformCertificateFilePath = '平台证书路径';
$platformCertificateInstance = PemUtil::loadCertificate($platformCertificateFilePath);
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateInstance);
$response = file_get_contents('php://input');
$data = json_decode(file_get_contents('php://input'),true);
$signature = $this->request->header('Wechatpay-Signature');
$timestamp = $this->request->header('Wechatpay-Timestamp');
$nonce = $this->request->header('Wechatpay-Nonce');
$serial = $this->request->header('Wechatpay-Serial');
if ($serial == $platformCertificateSerial) {
if (Crypto\Rsa::verify(Formatter::response($timestamp, $nonce, $response), $signature, $platformCertificateInstance)) {
if(!empty($data) && $data['event_type'] == 'TRANSACTION.SUCCESS' && $data['resource_type'] == 'encrypt-resource'){
$associated_data = $data['resource']['associated_data'];
$nonce = $data['resource']['nonce'];
$ciphertext = $data['resource']['ciphertext'];
$apiv3Key = config('apiv3Key');
$list = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $associated_data);
$list = json_decode($list,true);
if(!empty($list)){
if($list['trade_state'] == 'SUCCESS'){
try {
$record = Db::table('pay_record')->where("order_id = '$list[out_trade_no]' ")->find();
if(empty($record)){
$this->msg = '未找到该订单';
break;
}
} catch (\Exception $e) {
$statuscode = 500;
}
$log = [
'type' => 2,
'order_id' => $list['out_trade_no'],
'post_data' => json_encode($list,JSON_UNESCAPED_UNICODE),
'response_data' => $this->msg,
'create_time' => date('Y-m-d H:i:s')
];
Db::table('pay_log')->insert($log);
}
}
}
}
}
break;
default:
$statuscode = 500;
$this->msg = "请求方式错误";
break;
}
$this->success($this->msg,$this->ret_data,$this->code,$this->total,'',array('statuscode'=>$statuscode));
}
以上demo包含了最基本的支付、支付查询、退款、支付回调model,在使用的时候稍加修改就可以完美的实现微信服务商支付的功能了。
|