1.背景说明
最近做微信小程序的付费项目,IOS端小程序直接调用微信的JSAPI下单。违规记录如图。IOS端是不允许含有虚拟支付功能。所以专用客服服务付款
2.解决方案
用户点击页面联系客服->跳转至服务号->发送小程序给客服->客服返回付款链接->用户点击链接付款成功。那么,客服自动回复如何实现呢?
3.操作步骤
3.1小程序开启消息推送
第一步:填写服务器配置
登录小程序后台后,在「开发」-「开发设置」-「消息推送」中,管理员扫码启用消息服务,填写服务器地址(URL)、令牌(Token) 和 消息加密密钥(EncodingAESKey)等信息。
- URL: 开发者用来接收微信消息和事件的接口 URL。开发者所填写的URL 必须以 http:// 或 https:// 开头,分别支持 80 端口和 443 端口。
- Token: 可由开发者可以任意填写,用作生成签名(该 Token 会和接口 URL 中包含的 Token 进行比对,从而验证安全性)。
- EncodingAESKey: 由开发者手动填写或随机生成,将用作消息体加解密密钥。
同时,开发者可选择消息加解密方式:明文模式(默认)、兼容模式和安全模式。可以选择消息数据格式:XML 格式(默认)或 JSON 格式。
登录微信管理平台,点击开发管理->开发设置->往下拖找到消息推送。
第二步:验证消息的确来自微信服务器
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:
第三步:接收消息和事件
当某些特定的用户操作引发事件推送时(如用户向小程序客服发送消息、或者进入会话等情况),微信服务器会将消息(或事件)的数据包以 POST 请求发送到开发者配置的 URL,开发者可以依据自身业务逻辑进行响应。
微信服务器在将用户的消息发给开发者服务器地址后,微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。如果在调试中,发现用户无法收到响应的消息,可以检查是否消息处理超时。关于重试的消息排重,有 msgid 的消息推荐使用 msgid 排重。事件类型消息推荐使用 FromUserName + CreateTime 排重。
服务器收到请求必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试,否则,将出现严重的错误提示。详见下面说明:
- 直接回复success(推荐方式)
- 直接回复空串(指字节长度为0的空字符串,而不是结构体中content字段的内容为空)
- 若接口文档有指定返回内容,应按文档说明返回
对于客服消息,一旦遇到以下情况,微信会在小程序会话中向用户下发系统提示“该小程序客服暂时无法提供服务,请稍后再试”:
- 开发者在5秒内未回复任何内容
- 开发者回复了异常数据
3.验证消息来自微信
直接将微信发送给我们的echostr返回就好了,不用做解密。在页面点击提交就可以完成验证了。
@RequestMapping(value = "/checkWeixinValid",method=RequestMethod.GET)
public String checkWeixinValid(@RequestParam(name="signature")String signature,
@RequestParam(name="timestamp")String timestamp,
@RequestParam(name="nonce")String nonce,
@RequestParam(name="echostr")String echostr){
// .......
return echostr;
}
4.实现客服消息自动回复
重点:如果你不知道微信传给你的MsgType是什么类型的可以打个断点。查看下类型然后做逻辑处理。
/**
* 微信小程序客服消息自动回复
*
* @author 13
* @return
* @throws Exception
*/
@RequestMapping(value = "/checkWeixinValid", method = RequestMethod.POST)
@UserNotRequired
public String replyMessage(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String,String> map = null;
//从工具类中获取XML解析之后的map
try {
map = WeixinUtils.readWeixinXml(request);
} catch (IOException e) {
log.error("获取输入流失败", e);
} catch (DocumentException e) {
log.error("读取XML失败", e);
}
return appletCommonService.replyMessage(map);
}
WeixinUtils工具类
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author luboyang
* @Date 2021-02-25
*/
public class WeixinUtils {
/**
* 解析微信请求并读取XML
* @param request
* @return
* @throws IOException
* @throws DocumentException
*/
public static Map<String,String> readWeixinXml(HttpServletRequest request) throws IOException, DocumentException {
Map<String,String> map = new HashMap<String,String>();
//获取输入流
InputStream input = request.getInputStream();
//使用dom4j的SAXReader读取(org.dom4j.io.SAXReader;)
SAXReader sax = new SAXReader();
Document doc = sax.read(input);
//获取XML数据包根元素
Element root = doc.getRootElement();
//得到根元素的所有子节点
@SuppressWarnings("unchecked")
List<Element> elementList = root.elements();
//遍历所有节点并将其放进map
for(Element e : elementList){
map.put(e.getName(), e.getText());
}
//释放资源
input.close();
input = null;
return map;
}
}
sxhi
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.macro.contacts.logic.api.OrderLogic;
import com.macro.contacts.model.dto.OrderDTO;
import com.macro.contacts.utils.wx.WxAccessTokenRes;
import com.macro.contacts.utils.wx.WxConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
import java.util.Map;
/**
* @author luboyang
* @Date 2021-02-25
*/
@Service
@Slf4j
public class AppletCommonService {
public static final String MSG_TYPE = "miniprogrampage";
public static final String TYPE = "MsgType";
@Autowired
private OrderLogic orderLogic;
/**
* 小程序客服自动回复功能
*
* @param msg 微信传递的参数集合
* @return "SUCCEE"告知微信处理成功
* @throws Exception
* @author yixiu
*/
public String replyMessage(Map<String, String> msg) throws Exception {
System.out.println("客服消息自动回复 ====> start");
String opendId = msg.get("FromUserName");
Map<String, String> contenMap = Maps.newHashMapWithExpectedSize(2);
// 若用户消息包含关键字则自动回复
//并不是所有的消息我都需要回复,所以进行MsgType的判断。这处理我想要类型的消息
if (msg.get(TYPE).equals(MSG_TYPE)) {
//这边填写你要发送给用户的消息
contenMap.put("content", "请点击下发链接进行付款:"+""+orderLogic.h5CreateOrder(orderDTO,pagePath));
sendMessage(opendId, "text", contenMap);
// 此处将消息转发至人工客服,不然人工客服窗会没有消息
Map<String, Object> tranMap = Maps.newHashMapWithExpectedSize(4);
tranMap.put("ToUserName", msg.get("FromUserName"));
tranMap.put("FromUserName", msg.get("ToUserName"));
tranMap.put("CreateTime", msg.get("CreateTime"));
tranMap.put("MsgType", "transfer_customer_service");
return JSONObject.toJSONString(tranMap);
}
return "SUCCESS";
}
/**
* 客服消息自动回复
*
* @param opendId 接收者openId
* @param msgType 消息格式:text 文本;image 图片; link 图文链接
* @param contentMap 正文map
* @return
* @author yixiu
*/
private void sendMessage(String opendId, String msgType, Map<String, String> contentMap) throws UnsupportedEncodingException {
Map<String, Object> replyMessageMap = Maps.newHashMapWithExpectedSize(4);
replyMessageMap.put("touser", opendId);
replyMessageMap.put("msgtype", msgType);
replyMessageMap.put(msgType, contentMap);
String messageJson = JSONObject.toJSONString(replyMessageMap);
messageJson = new String(messageJson.getBytes(), "UTF-8");
String url = WxConstants.GET_ACCESS_TOKEN
.replace("APPID", "你的appid")
.replace("APPSECRET", "你的secret");
String response = HttpUtil.get(url);
if (response == null) {
return;
}
WxAccessTokenRes wxAccessTokenRes = JSONUtil.toBean(response, WxAccessTokenRes.class);
String geturl = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + wxAccessTokenRes.getAccess_token();
// 发送结果
HttpUtil.post(geturl, messageJson);
}
}
WxAccessTokenRess实体类
import lombok.Data;
@Data
public class WxAccessTokenRes {
/**
* 获取到的凭证
*/
private String access_token;
/**
* 凭证有效时间,单位:秒。目前是7200秒之内的值
*/
private Integer expires_in;
/**
* 错误码
*/
private Integer errcode;
/**
* 错误信息
*/
private String errmsg;
}
//api.weixin.qq.com/cgi-bin/mes… developers.weixin.qq.com/miniprogram…