最近出于个人需要折腾了一套微信免签支付的个人系统,下面介绍一下整体的一个实现方案 由于微信本身不提供免签的支付api,因此个人若想要实现免签支付,那么只能通过监听微信收款通知来实现了,因此整个系统的实现部分分为 收款服务端和监听客户端来实现
收款服务端实现
整个收款服务的实现思想是通过app实现对微信通知的监听,当监听到收款信息的时候则把收款金额上报给到服务端,服务端根据收款金额找到活跃订单然后回调支付成功接口,页面实时监测支付状态,当支付成功的时候则跳转成功界面。 具体如图:
那么在这整个流程里面需要注意的就是同时活跃的订单金额是不能重复的
订单创建
从简单点来说订单的创建仅仅是总数据库里面添加一条数据而已,但是在创建订单的时候需要考虑的几个问题
1、避免页面的重复刷新导致订单无限创建 对于用户刷新页面导致的订单一直创建的问题可以使用缓存策略,当发现来源是同一个ip,并且金额相同的时候则会从缓存里面直接获取当前的订单
2、对于重复金额的订单 由于系统是通过金额来确定唯一订单的,那么就意味着同一个商品被不同的用户打开的时候需要生成不同的金额,那么可以采用递增0.01或者递减0.01的策略来实现,不过意味着系统里面需要有更多的同金额的收款码与之对应
监听客户端实现(安卓app)
对于监听客户端主要是以app服务的形式安装在安卓机器上面,因此无论是ios用户还是安卓用户都更推荐在电脑上运行安卓模拟器来监听收款
通知监听
对于通知的监听可以通过一下代码来实现 1、开启通知监听
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.jenkin.jenkin_pay">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<application android:networkSecurityConfig="@xml/network_security_config" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Jenkinpay" tools:targetApi="n">
<activity android:name=".MainActivity" android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:exported="false" android:name="com.jenkin.jenkin_pay.NotificationMonitor" android:label="@string/app_name" android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService"/>
</intent-filter>
</service>
</application>
</manifest>
2、继承通知服务
public class NotificationMonitor extends NotificationListenerService {
@Override
public void onNotificationPosted(StatusBarNotification paramStatusBarNotification) {
System.out.println("接受到通知消息,在这里解析收款信息,上传到服务器");
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
}
@Override
public void onListenerConnected(){
initAppHeart();
new Handler(Looper.getMainLooper()).post(new Runnable()
{
public void run()
{
Toast.makeText(NotificationMonitor.this.getApplicationContext(), "监听服务启动成功", Toast.LENGTH_SHORT).show();
}
});
}
省电方案
上面的方案已经能够实现免签支付了,但是由于需要实时监控收款通知,那么私人主机就得实时的开机在线,但是由于订单并不一定随时都有,而家里的主机一般功率都不低,每天7*24小时在线的话一个月下来也是一笔不小的电费开支,因此可以通过随用随启的方式减少电量的消耗,那么如此就需要实现自动的开机和关机或者自动的唤醒和睡眠
wol网络唤醒(推荐)
使用wol唤醒的方式需要主板和操作系统同时支持才行,具体的设置我这里就不想详细描述了,大概就是在主板的设置界面打开网络唤醒选项,同时windows在网卡设置里面允许网络唤醒就可以了,然后在nodemcu单片机内部向局域网内部指定的mac地址发送网络唤醒包就可以实现了
使用继电器接笔记本电源键
如果使用的是笔记本电脑作为监听设备的话那么wol网络唤醒可能无法使用,因为目前看来大部分的笔记本主板都不支持这个功能,那也不是没有办法的 1、有条件的可以拆开笔记本,找到开机键的两根开机排线,然后焊接处两根排线出来,然后这两根排线在街上继电器,然后使用单片机控制这个继电器,那么这样就可以实现笔记本的开机关机或者唤醒了 2、对于不想拆开笔记本的也可以通过在电源按键或者其他部位固定一个步进电机或者别的东西,通过单片机来控制这个步进电机或者其他设备,来实现按压的效果,从而来实现笔记本的开机或者唤醒的操作
网络唤醒控制器(esp8266)
上面的两个操作都涉及到了单片机控制器的部分,这部分的实现代码如下:
#include <Arduino_JSON.h>
#include <Pinger.h>
#include <PingerResponse.h>
extern "C"
{
#include <lwip/icmp.h> // needed for icmp packet definitions
}
#include <WiFiUdp.h>
#include <WakeOnLan.h>
#include <ESP8266WiFi.h>
//const char *serverIP = "192.168.3.108";
const char *serverIP = "xxx.xxx.xxx";
WiFiUDP UDP;
WakeOnLan WOL(UDP);
int serverPort = 9644;
bool bConnected = false;
char buff[1024];
int nm = 0;
Pinger pinger;
PingerResponse response;
WiFiClient client;
boolean success = false;
/** {"code":0,"data":"12313"} */
void setup() {
Serial.println("program start");
Serial.begin(2000000);
pinMode(5, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
smartConfig(); //这里调用一键配网函数,替换上面的普通配网
}
void loop() {
int i = 0;
int j = 0;
while (1)
{
if (i >= 1000)
{
char *s = "{\"code\":100,\"data\":\"10001\"}";
Serial.printf("connect status %d \n", client.status());
if (client.status() != 4)
{
bConnected = false;
}
if (WiFi.status() != WL_CONNECTED)
{
smartConfig();
}
int num = client.println(s);
client.flush();
if (num == 0)
{
bConnected = false;
}
i = 0;
}
delay(1);
i++;
if (bConnected == false)
{
if (!client.connect(serverIP, serverPort))
{
Serial.println("connection failed");
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
if (WiFi.status() != WL_CONNECTED)
{
smartConfig();
}
continue;
}
bConnected = true;
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("connection ok");
}
else if (client.available())
{
Serial.println("data is coming");
while (client.available())
{
buff[nm++] = client.read();
if (nm >= 1023)
break;
}
buff[nm] = 0x00;
nm = 0;
Serial.println(buff);
//处理网络请求
dealRequest(buff);
}
}
// wait for a second
// put your main code here, to run repeatedly:
}
//==================处理网络请求==========================
/** //网络请求代码 int SHUTDOWN_COMP = 0;//关机 int START_COMP = 1;//开机 int WEAK_UP_COMP = 2;//唤醒 int RELOAD_COMP = 4;//重新载入电脑列表 int PINT_COMP = 5;//检测电脑状态 //网络响应代码 int SUCCESS = 200; //请求执行成功 int NOT_EXIST_MAC = 501; //Mac地址不存在 int HEART = 100;//心跳检测 */
void dealRequest(char* buff) {
JSONVar res = JSON.parse(buff);
int code = (int) res["code"];
int seq = (int) res["seq"];
const char* realData = (const char*) res["data"];
switch (code) {
//开机
case 1: {
JSONVar openData = JSON.parse(realData);
int point = (int)openData["point"];
digitalWrite(point, HIGH);
delay(1000);
digitalWrite(point, LOW);
break;
}
//关机
case 0: {
JSONVar shutdownData = JSON.parse(realData);
int point = (int)shutdownData["point"];
digitalWrite(point, HIGH);
delay(5000);
digitalWrite(point, LOW);
break;
}
//唤醒
case 2: {
JSONVar weak = JSON.parse(realData);
const char *MACAddress = (const char*)weak["mac"];
WOL.sendMagicPacket(MACAddress);
break;
}
//重载
case 4:
break;
//收到带转发的udp消息
case 6:{
JSONVar udpData = JSON.parse(realData);
const char *ip = (const char*)udpData["ip"];
int port = (int)udpData["port"];
const char *msg = (const char*)udpData["msg"];
sendUdp(ip,port,msg);
break;
}
//ping
case 5: {
JSONVar pingData = JSON.parse(realData);
int p1 = (int)pingData["p1"];
int p2 = (int)pingData["p2"];
int p3 = (int)pingData["p3"];
int p4 = (int)pingData["p4"];
int r = ping(p1, p2, p3, p4);
char *sta = "{\"code\":200,\"seq\":%d,\"data\":%d}";
char stres[1024] = { 0 };
sprintf(stres, sta, seq, r);
client.println(stres);
client.flush();
break;
}
default:
break;
}
}
void sendUdp(const char* ip,int port,const char* msg){
Serial.printf("send msg:%s, %d ,%s \r\n", ip,port,msg);
//向udp工具发送消息
UDP.beginPacket(ip, port);//配置远端ip地址和端口
UDP.write(msg);//把数据写入发送缓冲区
UDP.endPacket();//发送数据
}
//==========将下列代码添加到需要一键配网项目代码的最后==========
void smartConfig() {
WiFi.begin();
while (1)
{
if (WiFi.status() == WL_CONNECTED)
{
Serial.println("AutoConfig Success");
Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str());
Serial.printf("PSW:%s\r\n", WiFi.psk().c_str());
// WiFi.printDiag(Serial); //打印关键的Wi-Fi诊断信息,信息比较多
break;
}
else
{
Serial.printf("AutoConfig Waiting... \n ");
Serial.println(WiFi.status());
for (int j = 5; j > 0; j--)
{
digitalWrite(LED_BUILTIN, HIGH);
delay(200);
digitalWrite(LED_BUILTIN, LOW);
delay(200);
}
// 等待长按某个按键调用smart() 重新配网
// smart()
}
}
}
void smart() {
if (WiFi.status() != WL_CONNECTED)
{
Serial.println("AutoConfig Faild!");
WiFi.mode(WIFI_STA);
Serial.println("\r\nWait for Smartconfig");
WiFi.beginSmartConfig();
while (1)
{
Serial.print(".");
digitalWrite(LED_BUILTIN, HIGH);
if (WiFi.smartConfigDone())
{
Serial.println("SmartConfig Success");
Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str());
Serial.printf("PSW:%s\r\n", WiFi.psk().c_str());
WiFi.setAutoConnect(true); // 设置自动连接
digitalWrite(LED_BUILTIN, LOW);
break;
}
delay(3000);
}
}
}
// ================ping 指定的电脑======================
// 返回1 成功
int ping(int p1, int p2, int p3, int p4) {
success = false;
pinger.OnReceive([](const PingerResponse & response)
{
if (response.ReceivedResponse)
{
Serial.printf(
"Reply from %s: bytes=%d time=%lums TTL=%d\n",
response.DestIPAddress.toString().c_str(),
response.EchoMessageSize - sizeof(struct icmp_echo_hdr),
response.ResponseTime,
response.TimeToLive);
success = true;
}
else
{
Serial.printf("Request timed out.\n");
}
// Return true to continue the ping sequence.
// If current event returns false, the ping sequence is interrupted.
return false;
});
pinger.OnEnd([](const PingerResponse & response)
{
// Evaluate lost packet percentage
float loss = 100;
if (response.TotalReceivedResponses > 0)
{
loss = (response.TotalSentRequests - response.TotalReceivedResponses) * 100 / response.TotalSentRequests;
}
// Print packet trip data
Serial.printf(
"Ping statistics for %s:\n",
response.DestIPAddress.toString().c_str());
Serial.printf(
" Packets: Sent = %lu, Received = %lu, Lost = %lu (%.2f%% loss),\n",
response.TotalSentRequests,
response.TotalReceivedResponses,
response.TotalSentRequests - response.TotalReceivedResponses,
loss);
// Print time information
if (response.TotalReceivedResponses > 0)
{
Serial.printf("Approximate round trip times in milli-seconds:\n");
Serial.printf(
" Minimum = %lums, Maximum = %lums, Average = %.2fms\n",
response.MinResponseTime,
response.MaxResponseTime,
response.AvgResponseTime);
}
// Print host data
Serial.printf("Destination host data:\n");
Serial.printf(
" IP address: %s\n",
response.DestIPAddress.toString().c_str());
if (response.DestMacAddress != nullptr)
{
Serial.printf(
" MAC address: " MACSTR "\n",
MAC2STR(response.DestMacAddress->addr));
}
if (response.DestHostname != "")
{
Serial.printf(
" DNS name: %s\n",
response.DestHostname.c_str());
}
return true;
});
pinger.Ping(IPAddress(p1, p2, p3, p4));
delay(20);
if (success)
{
Serial.print("SUCCESS!\n");
return 1;
}
else
{
Serial.println("Error during ping command.");
return -1;
}
}
windows系统休眠设置
对于上面的描述主要还是实现了电脑的唤醒,但是并没有实现电脑的远程休眠,那么对于电脑的远程休眠可以通过调用windows的命令来实现,那么最简单的的办法就是在windows机器上面开启一个udp服务,然后通过外网想单片机发送udp指令,然后再将udp指令转发到指定的电脑实现电脑的休眠或者关机操作 而这个udp服务则可以加入到开机启动项 对于udp服务的代码如下:
package main
import (
"fmt"
"io"
"log"
"net"
"os"
"os/exec"
"sync"
"syscall"
"time"
)
var udpConn *net.UDPConn
var srvAddr *net.UDPAddr
var err error
var mutex sync.Mutex
const (
// 控制输出日志信息的细节,不能控制输出的顺序和格式。
// 输出的日志在每一项后会有一个冒号分隔:例如2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
Ldate = 1 << iota // 日期:2009/01/23
Ltime // 时间:01:23:23
Lmicroseconds // 微秒级别的时间:01:23:23.123123(用于增强Ltime位)
Llongfile // 文件全路径名+行号: /a/b/c/d.go:23
Lshortfile // 文件名+行号:d.go:23(会覆盖掉Llongfile)
LUTC // 使用UTC时间
LstdFlags = Ldate | Ltime // 标准logger的初始值
)
func init() {
logFile, err := os.OpenFile("C:\\sleepServer.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println("open log file failed, err:", err)
return
}
multiWriter := io.MultiWriter(os.Stdout, logFile)
log.SetOutput(multiWriter)
log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
}
func main() {
//组织一个udp地址结构,指定服务器的IP+port
//srvAddr是地址结构
srvAddr, err = net.ResolveUDPAddr("udp", "0.0.0.0:60123")
//访问命令:nc -u 127.0.0.1 8006
if err != nil {
fmt.Println("ResolveUDPAddr err:", err)
return
}
//创建用户通信的socket:udpConn
udpConn, err = net.ListenUDP("udp", srvAddr)
if err != nil {
log.Println("ListenUD err:", err)
return
}
//fmt.Println(udpConn.)
defer udpConn.Close()
log.Println("udp 服务器socket,创建完成!!!")
log.Println("udp 服务器地址结构,创建完成!!!")
for true {
err := udpConn.SetDeadline(time.Now().Add(30 * time.Second))
if err != nil {
log.Println("超时设置错误")
}
//读取客户端发送的数据
buf := make([]byte, 4096)
//返回3个值。分别是读取到的字节数,客户端的地址,error
log.Println("等待数据...")
n, cltAddr, err := udpConn.ReadFromUDP(buf)
if err != nil {
log.Println("ReadFromUDP err:", err)
udpConn.Close()
udpConn, _ = net.ListenUDP("udp", srvAddr)
}
msg := string(buf[:n])
//模拟处理数据
log.Printf("服务器读到%vd的数据:%s\n", cltAddr, msg)
if "sleep now"==msg {
fmt.Println("开始休眠")
sleep()
}
}
log.Println("every thing is over!")
}
func sleep() {
cmd := exec.Command("cmd.exe")
script:="rundll32.exe powrprof.dll,SetSuspendState 0,1,0"
fmt.Println(fmt.Sprintf(`/c %s`, script))
//核心点,直接修改执行命令方式
cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: fmt.Sprintf(`/c %s`, script), HideWindow: true}
output, err := cmd.Output()
fmt.Printf("output:\n%s\n", output)
if err != nil {
fmt.Printf("error: %+v\n", err)
}
}
电脑管理平台的预览图: 订单管理的预览图:
微信搜索关注公众号 jenkin技术,回复 资料,可免费领取十万字大厂java面经
今天的文章个人收款的实现方案分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:http://bianchenghao.cn/20964.html