Hyperf + uni-app 使用 EasyWechat 实现微信小程序登录和支付
安装 EasyWechat
composer require overtrue/wechat:~4.0 -vvv
修改 SWOOLE_HOOK_FLAGS
编辑 bin/hyperf.php
文件
<?php
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL | SWOOLE_HOOK_CURL);
配置
创建配置文件 config/autoload/wechat.php
touch config/autoload/wechat.php
<?php
declare(strict_types=1);
return [
/*
* 小程序
*/
'mini_program' => [
'default' => [
'app_id' => env('WECHAT_MINI_PROGRAM_APPID', ''),
'secret' => env('WECHAT_MINI_PROGRAM_SECRET', ''),
'token' => env('WECHAT_MINI_PROGRAM_TOKEN', ''),
'aes_key' => env('WECHAT_MINI_PROGRAM_AES_KEY', ''),
],
],
/*
* 微信支付
*/
'payment' => [
'default' => [
'sandbox' => env('WECHAT_PAYMENT_SANDBOX', false),
'app_id' => env('WECHAT_PAYMENT_APPID', ''),
'mch_id' => env('WECHAT_PAYMENT_MCH_ID', 'your-mch-id'),
'key' => env('WECHAT_PAYMENT_KEY', 'key-for-signature'),
'cert_path' => env('WECHAT_PAYMENT_CERT_PATH', 'path/to/cert/apiclient_cert.pem'), // XXX: 绝对路径!!!!
'key_path' => env('WECHAT_PAYMENT_KEY_PATH', 'path/to/cert/apiclient_key.pem'), // XXX: 绝对路径!!!!
'notify_url' => env('WECHAT_PAYMENT_NOTIFY_URL', ''), // 默认支付结果通知地址
],
// ...
],
];
编辑 .env 文件
# EasyWechat 小程序账号
WECHAT_MINI_PROGRAM_APPID=wx46f4f2***
WECHAT_MINI_PROGRAM_SECRET=28ddcd98d139a53*****
WECHAT_MINI_PROGRAM_TOKEN=
WECHAT_MINI_PROGRAM_AES_KEY=
# 支付
WECHAT_PAYMENT_SANDBOX=false
WECHAT_PAYMENT_APPID=wx46f4f2***
WECHAT_PAYMENT_MCH_ID=1517****
WECHAT_PAYMENT_KEY=Mm4vhqTUQaskidBr*****
WECHAT_PAYMENT_CERT_PATH=
WECHAT_PAYMENT_KEY_PATH=
WECHAT_PAYMENT_NOTIFY_URL=http://yourdomain/payments/notify
小程序登录
mkdir -p app/Kernel/Oauth
touch app/Kernel/Oauth/WeChatFactory.php
文件内容如下:
<?php
declare(strict_types=1);
namespace App\Kernel\Oauth;
use EasyWeChat\Factory;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Guzzle\CoroutineHandler;
use Hyperf\Guzzle\HandlerStackFactory;
use Overtrue\Socialite\Providers\AbstractProvider;
use Psr\Container\ContainerInterface;
use Hyperf\Utils\ApplicationContext;
use Psr\SimpleCache\CacheInterface;
class WeChatFactory
{
/**
* @var ContainerInterface
*/
protected $container;
private $config;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->config = $container->get(ConfigInterface::class)->get('wechat.mini_program.default');
// 设置 OAuth 授权的 Guzzle 配置
AbstractProvider::setGuzzleOptions([
'http_errors' => false,
'handler' => HandlerStack::create(new CoroutineHandler()),
]);
}
/**
* @return \EasyWeChat\MiniProgram\Application
*/
public function create()
{
$app = Factory::miniProgram($this->config);
// 设置 HttpClient,当前设置没有实际效果,在数据请求时会被 guzzle_handler 覆盖,但不保证 EasyWeChat 后面会修改这里。
$config = $app['config']->get('http', []);
$config['handler'] = $this->container->get(HandlerStackFactory::class)->create();
$app->rebind('http_client', new Client($config));
// 重写 Handler
$app['guzzle_handler'] = $this->container->get(HandlerStackFactory::class)->create();
// 重新 Cache
$app['cache'] = ApplicationContext::getContainer()->get(CacheInterface::class);
return $app;
}
}
新建登录控制器
php bin/hyperf.php gen:controller AuthController
大致内容如下:
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Kernel\Oauth\WeChatFactory;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
/**
* @AutoController()
* Class AuthController
* @package App\Controller
*/
class AuthController
{
/**
* @Inject()
* @var WeChatFactory
*/
protected $factory;
public function login(RequestInterface $request,ResponseInterface $response)
{
$code = $request->input('code');
$encryptedData = $request->input('encrypted_data');
$iv = $request->input('iv');
$app = $this->factory->create();
$session = $app->auth->session($code);
$userInfo = $app->encryptor->decryptData($session['session_key'], $iv, $encryptedData);
var_dump($userInfo);
return $userInfo;
}
}
前端 uni-app 登录
<template>
<view>
<button open-type="getUserInfo" @getuserinfo="login" withCredentials="true">注册</button>
</view>
</template>
<script>
export default {
data() {},
onLoad() {},
methods: {
async login(res) {
var encryptedData = res.detail.encryptedData
var iv = res.detail.iv;
var [, res] = await uni.login({
provider: 'weixin'
});
var code = res.code;
uni.request({
url: 'http://127.0.0.1:9501/auth/login',
method: 'POST',
data: {
code: code,
encrypted_data: encryptedData,
iv: iv
},
success: (res) => {
console.log(res.data);
}
});
},
},
}
</script>
小程序支付
新建一个 app\Kernel\Payment\WeChatFactory.php
文件
mkdir -p app/Kernel/Payment
touch app/Kernel/Payment/WeChatFactory.php
<?php
declare(strict_types=1);
namespace App\Kernel\Payment;
use EasyWeChat\Factory;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Guzzle\CoroutineHandler;
use Hyperf\Guzzle\HandlerStackFactory;
use Overtrue\Socialite\Providers\AbstractProvider;
use Psr\Container\ContainerInterface;
use Hyperf\Utils\ApplicationContext;
use Psr\SimpleCache\CacheInterface;
class WeChatFactory
{
/**
* @var ContainerInterface
*/
protected $container;
protected $paymentConfig;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->paymentConfig = $container->get(ConfigInterface::class)->get('wechat.payment.default');
// 设置 OAuth 授权的 Guzzle 配置
AbstractProvider::setGuzzleOptions([
'http_errors' => false,
'handler' => HandlerStack::create(new CoroutineHandler()),
]);
}
/**
* @return \EasyWeChat\Payment\Application
*/
public function payment()
{
$app = Factory::payment($this->paymentConfig);
// 设置 HttpClient,当前设置没有实际效果,在数据请求时会被 guzzle_handler 覆盖,但不保证 EasyWeChat 后面会修改这里。
$config = $app['config']->get('http', []);
$config['handler'] = $this->container->get(HandlerStackFactory::class)->create();
$app->rebind('http_client', new Client($config));
// 重写 Handler
$app['guzzle_handler'] = $this->container->get(HandlerStackFactory::class)->create();
// 重新 Cache
$app['cache'] = ApplicationContext::getContainer()->get(CacheInterface::class);
return $app;
}
}
新建支付控制器
php bin/hyperf.php gen:controller PaymentsController
代码如下:
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Kernel\Payment\WeChatFactory;
use EasyWeChat\Kernel\Exceptions\Exception;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Annotation\Middleware;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Hyperf\Logger\LoggerFactory;
use Symfony\Component\HttpFoundation\Request;
use function EasyWeChat\Kernel\Support\generate_sign;
/**
* @AutoController()
* Class PaymentsController
* @package App\Controller
*/
class PaymentsController extends AbstractController
{
/**
* @Inject()
* @var WeChatFactory
*/
protected $factory;
/**
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
protected $rechargeDao;
public function __construct(LoggerFactory $loggerFactory)
{
// 第一个参数对应日志的 name, 第二个参数对应 config/autoload/logger.php 内的 key
$this->logger = $loggerFactory->get('log', 'default');
}
public function index(RequestInterface $request, ResponseInterface $response)
{
$this->logger->info("支付开始");
$app = $this->factory->payment();
$result = $app->order->unify([
'body' => 'QQ 会员充值',
'out_trade_no' => time(),
'total_fee' => 10,
//'spbill_create_ip' => '123.12.12.123', // 可选,如不传该参数,SDK 将会自动获取相应 IP 地址
'notify_url' => config('wechat.payment.default.notify_url'), // 支付结果通知网址,如果不设置则会使用配置里的默认地址
'trade_type' => 'JSAPI', // 请对应换成你的支付方式对应的值类型
'openid' => 'ogPdd5bAEOp3tZL1nxavbx7'
]);
if ($result['return_code'] === 'SUCCESS' && $result['return_msg'] === 'OK')
{
// 二次验签
$params = [
'appId' => config('wechat.payment.default.app_id'),
'timeStamp' => time(),
'nonceStr' => $result['nonce_str'],
'package' => 'prepay_id=' . $result['prepay_id'],
'signType' => 'MD5',
];
// config('wechat.payment.default.key')为商户的key
$params['paySign'] = generate_sign($params, config('wechat.payment.default.key'));
return $params;
}
else{
//
}
}
public function notify(RequestInterface $request, ResponseInterface $response)
{
$this->logger->info("收到回调");
$app = $this->factory->payment();
$get = $this->request->getQueryParams();
$post = $this->request->getParsedBody();
$cookie = $this->request->getCookieParams();
$files = $this->request->getUploadedFiles();
$server = $this->request->getServerParams();
$xml = $this->request->getBody()->getContents();
$app['request'] = new Request($get,$post,[],$cookie,$files,$server,$xml);
$app->handlePaidNotify(function ($message, $fail) {
$this->logger->error($message['out_trade_no']);
$this->logger->info($message['return_code']);
$this->logger->info('回调数据'.json_encode($message));
if ($message['return_code'] === 'SUCCESS') { // return_code 表示通信状态,不代表支付状态
// 用户是否支付成功
}
// 用户支付失败
return false;
});
}
}
小程序掉起支付代码
<template>
<view>
<button @tap="payment">服务订单支付</button>
</view>
</template>
<script>
export default {
data() {},
onLoad() {},
methods: {
async payment() {
const token = uni.getStorageSync('token');
uni.getProvider({
service: 'payment',
success: function(res) {
var provider = res.provider[0];
uni.request({
url: 'http:127.0.0.1:9501/payments/index',
method: 'POST',
data: {},
success: (res) => {
uni.requestPayment({
provider: provider,
orderInfo: '腾讯充值中心-QQ会员充值',
timeStamp: res.data.timeStamp.toString(),
nonceStr: res.data.nonceStr,
package: res.data.package,
signType: res.data.signType,
paySign: res.data.paySign,
_deguh: 1,
success(res) {
console.log(res);
},
fail(res) {
console.log(res);
}
})
}
});
}
})
},
},
}
</script>
至于微信回调地址就得需要生产环境来测试了,沙箱环境我没有测试过,也懒的搞。这里截张生产环境下的回调 Log 信息
关于极客返利
极客返利 是由我个人开发的一款网课返利、返现平台。包含 极客时间返现、拉勾教育返现、掘金小册返现、GitChat返现。目前仅包含这几个平台。后续如果有需要可以考虑其他平台。 简而言之就是:你买课,我返现。让你花更少的钱,就可以买到课程。
版权许可
本作品采用 知识共享署名 4.0 国际许可协议 进行许可。转载无需与我联系,但须注明出处,注明文章来源 Hyperf + uni-app 使用 EasyWechat 实现微信小程序登录和支付
联系我
