springboot+微信小程序实现微信支付【统一下单】

说明:

1)微信支付必须有营业执照才可以申请

2)微信支付官方api是全套的,我这是抽取其中的统一下单api,做了一个简单的封装

首先看看微信支付

商户系统和微信支付系统主要交互:

1、小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API

2、商户server调用支付统一下单,api参见公共api【统一下单API

3、商户server调用再次签名,api参见公共api【再次签名

4、商户server接收支付通知,api参见公共api【支付结果通知API

5、商户server查询支付结果,api参见公共api【查询订单API

注意上面有两次签名  

1.配置文件类

 1  2
 3 public final class WxConfig {
 4     public final static String appId="wxe86f60xxxxxxx"; // 小程序appid
 5     public final static String mchId="15365xxxxx";// 商户ID
 6     public final static String key="Ucsdfl782167bjslNCJD129863skkqoo"; // 跟微信支付约定的密钥
 7     public final static String notifyPath="/admin/wxnotify"; // 回调地址
 8     public final static String payUrl="https://api.mch.weixin.qq.com/pay/unifiedorder"; // 统一下单地址
 9     public final static String tradeType="JSAPI"; // 支付方式
10
11 }

2.微信工具类,统一下单,签名,生成随机字符串。。

  4 import lombok.extern.slf4j.Slf4j;
  5 import org.apache.http.HttpEntity;
  6 import org.apache.http.HttpResponse;
  7 import org.apache.http.client.HttpClient;
  8 import org.apache.http.client.config.RequestConfig;
  9 import org.apache.http.client.methods.HttpPost;
 10 import org.apache.http.config.RegistryBuilder;
 11 import org.apache.http.conn.socket.ConnectionSocketFactory;
 12 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
 13 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
 14 import org.apache.http.entity.StringEntity;
 15 import org.apache.http.impl.client.HttpClientBuilder;
 16 import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
 17 import org.apache.http.util.EntityUtils;
 18 import org.slf4j.Logger;
 19 import org.slf4j.LoggerFactory;
 20 import org.w3c.dom.Document;
 21 import org.w3c.dom.Element;
 22 import org.w3c.dom.Node;
 23 import org.w3c.dom.NodeList;
 24
 25 import javax.crypto.Mac;
 26 import javax.crypto.spec.SecretKeySpec;
 27 import javax.xml.XMLConstants;
 28 import javax.xml.parsers.DocumentBuilder;
 29 import javax.xml.parsers.DocumentBuilderFactory;
 30 import javax.xml.parsers.ParserConfigurationException;
 31 import javax.xml.transform.OutputKeys;
 32 import javax.xml.transform.Transformer;
 33 import javax.xml.transform.TransformerFactory;
 34 import javax.xml.transform.dom.DOMSource;
 35 import javax.xml.transform.stream.StreamResult;
 36 import java.io.ByteArrayInputStream;
 37 import java.io.InputStream;
 38 import java.io.StringWriter;
 39 import java.security.MessageDigest;
 40 import java.security.SecureRandom;
 41 import java.time.Instant;
 42 import java.util.*;
 43
 44 @Slf4j
 45 public class WxUtil {
 46     private static final String WXPAYSDK_VERSION = "WXPaySDK/3.0.9";
 47     private static final String USER_AGENT = WXPAYSDK_VERSION +
 48             " (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") +
 49             ") Java/" + System.getProperty("java.version") + " HttpClient/" + HttpClient.class.getPackage().getImplementationVersion();
 50
 51     private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 52     private static final Random RANDOM = new SecureRandom();
 53     // 统一下单接口
 54     public static Map<String, String>  unifiedOrder(Map<String, String> reqData) throws Exception {
 55         // map格式转xml 方法在下面
 56         String reqBody = mapToXml(reqData);
 57         // 发起一次统一下单的请求 方法内容在下面
 58         String responseBody = requestOnce(WxConfig.payUrl, reqBody);
 59         // 将得到的结果由xml格式转为map格式 方法内容在下面
 60         Map<String,String> response= processResponseXml(responseBody);
 61         // 得到prepayId
 62         String prepayId = response.get("prepay_id");
 63         // 组装参数package_str 为什么这样? 因为二次签名微信规定这样的格式
 64         String package_str = "prepay_id="+prepayId;
 65         Map<String,String> payParameters = new HashMap<>();
 66         long epochSecond = Instant.now().getEpochSecond();
 67         payParameters.put("appId",WxConfig.appId);
 68         payParameters.put("nonceStr", WxUtil.generateNonceStr());
 69         payParameters.put("package", package_str);
 70         payParameters.put("signType", SignType.MD5.name());
 71         payParameters.put("timeStamp", String.valueOf(epochSecond));
 72         // 二次签名
 73         payParameters.put("paySign", WxUtil.generateSignature(payParameters, WxConfig.key, SignType.MD5));
 74         // 返回签名后的map
 75         return payParameters;
 76     }
 77
 78
 79     /**
 80      * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
 81      *
 82      * @param data 待签名数据
 83      * @param key API密钥
 84      * @param signType 签名方式
 85      * @return 签名
 86      */
 87     public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
 88         Set<String> keySet = data.keySet();
 89         String[] keyArray = keySet.toArray(new String[keySet.size()]);
 90         Arrays.sort(keyArray);
 91         StringBuilder sb = new StringBuilder();
 92         for (String k : keyArray) {
 93             if (k.equals("sign")) {
 94                 continue;
 95             }
 96             if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
 97                 sb.append(k).append("=").append(data.get(k).trim()).append("&");
 98         }
 99         sb.append("key=").append(key);
100         if (SignType.MD5.equals(signType)) {
101             return MD5(sb.toString()).toUpperCase();
102         }
103         else if (SignType.HMACSHA256.equals(signType)) {
104             return HMACSHA256(sb.toString(), key);
105         }
106         else {
107             throw new Exception(String.format("Invalid sign_type: %s", signType));
108         }
109     }
110
111     /**
112      * 生成 MD5
113      *
114      * @param data 待处理数据
115      * @return MD5结果
116      */
117     private static String MD5(String data) throws Exception {
118         MessageDigest md = MessageDigest.getInstance("MD5");
119         byte[] array = md.digest(data.getBytes("UTF-8"));
120         StringBuilder sb = new StringBuilder();
121         for (byte item : array) {
122             sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
123         }
124         return sb.toString().toUpperCase();
125     }
126
127     public static String generateNonceStr() {
128         char[] nonceChars = new char[32];
129         for (int index = 0; index < nonceChars.length; ++index) {
130             nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
131         }
132         return new String(nonceChars);
133     }
134
135     public static String mapToXml(Map<String, String> data) throws Exception {
136        Document document = newDocument();
137         Element root = document.createElement("xml");
138         document.appendChild(root);
139         for (String key: data.keySet()) {
140             String value = data.get(key);
141             if (value == null) {
142                 value = "";
143             }
144             value = value.trim();
145             Element filed = document.createElement(key);
146             filed.appendChild(document.createTextNode(value));
147             root.appendChild(filed);
148         }
149         TransformerFactory tf = TransformerFactory.newInstance();
150         Transformer transformer = tf.newTransformer();
151         DOMSource source = new DOMSource(document);
152         transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
153         transformer.setOutputProperty(OutputKeys.INDENT, "yes");
154         StringWriter writer = new StringWriter();
155         StreamResult result = new StreamResult(writer);
156         transformer.transform(source, result);
157         String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
158         try {
159             writer.close();
160         }
161         catch (Exception ex) {
162         }
163         return output;
164     }
165
166 // 判断签名是否有效
167     private static Map<String, String> processResponseXml(String xmlStr) throws Exception {
168         String RETURN_CODE = "return_code";
169         String return_code;
170         Map<String, String> respData = xmlToMap(xmlStr);
171         if (respData.containsKey(RETURN_CODE)) {
172             return_code = respData.get(RETURN_CODE);
173         }
174
175         else {
176             throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
177         }
178
179         if (return_code.equals("FAIL")) {
180             return respData;
181         }
182         else if (return_code.equals("SUCCESS")) {
183             if (isResponseSignatureValid(respData)) {
184                 return respData;
185             }
186             else {
187                 throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
188             }
189         }
190         else {
191             throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
192         }
193     }
194 // 判断签名
195     private static boolean isResponseSignatureValid(Map<String, String> data) throws Exception {
196         String signKeyword = "sign";
197         if (!data.containsKey(signKeyword) ) {
198             return false;
199         }
200         String sign = data.get(signKeyword);
201         return generateSignature(data, WxConfig.key, SignType.MD5).equals(sign);
202     }
203
204 // 发起一次请求
205     private static String requestOnce(String payUrl, String data) throws Exception {
206         BasicHttpClientConnectionManager connManager;
207         connManager = new BasicHttpClientConnectionManager(
208                 RegistryBuilder.<ConnectionSocketFactory>create()
209                         .register("http", PlainConnectionSocketFactory.getSocketFactory())
210                         .register("https", SSLConnectionSocketFactory.getSocketFactory())
211                         .build(),
212                 null,
213                 null,
214                 null
215         );
216
217         HttpClient httpClient = HttpClientBuilder.create()
218                 .setConnectionManager(connManager)
219                 .build();
220         HttpPost httpPost = new HttpPost(payUrl);
221
222         RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(8000).setConnectTimeout(6000).build();
223         httpPost.setConfig(requestConfig);
224
225         StringEntity postEntity = new StringEntity(data, "UTF-8");
226         httpPost.addHeader("Content-Type", "text/xml");
227         httpPost.addHeader("User-Agent", USER_AGENT + " " + WxConfig.mchId);
228         httpPost.setEntity(postEntity);
229
230         HttpResponse httpResponse = httpClient.execute(httpPost);
231         HttpEntity httpEntity = httpResponse.getEntity();
232         return EntityUtils.toString(httpEntity, "UTF-8");
233
234     }
235
236
237
238     private static String HMACSHA256(String data, String key) throws Exception {
239         Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
240         SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
241         sha256_HMAC.init(secret_key);
242         byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
243         StringBuilder sb = new StringBuilder();
244         for (byte item : array) {
245             sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
246         }
247         return sb.toString().toUpperCase();
248     }
249
250     private static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
251         DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
252         documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
253         documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
254         documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
255         documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
256         documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
257         documentBuilderFactory.setXIncludeAware(false);
258         documentBuilderFactory.setExpandEntityReferences(false);
259
260         return documentBuilderFactory.newDocumentBuilder();
261     }
262
263     private static Document newDocument() throws ParserConfigurationException {
264         return newDocumentBuilder().newDocument();
265     }
266
267
268     public static Map<String, String> xmlToMap(String strXML) throws Exception {
269         try {
270             Map<String, String> data = new HashMap<String, String>();
271             DocumentBuilder documentBuilder = newDocumentBuilder();
272             InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
273             org.w3c.dom.Document doc = documentBuilder.parse(stream);
274             doc.getDocumentElement().normalize();
275             NodeList nodeList = doc.getDocumentElement().getChildNodes();
276             for (int idx = 0; idx < nodeList.getLength(); ++idx) {
277                 Node node = nodeList.item(idx);
278                 if (node.getNodeType() == Node.ELEMENT_NODE) {
279                     org.w3c.dom.Element element = (org.w3c.dom.Element) node;
280                     data.put(element.getNodeName(), element.getTextContent());
281                 }
282             }
283             try {
284                 stream.close();
285             } catch (Exception ex) {
286                 // do nothing
287             }
288             return data;
289         } catch (Exception ex) {
290             getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
291             throw ex;
292         }
293
294     }
295     /**
296      * 日志
297      * @return
298      */
299     private static Logger getLogger() {
300         Logger logger = LoggerFactory.getLogger("wxpay java sdk");
301         return logger;
302     }
303
304
305
306     /**
307      * 判断签名是否正确
308      *
309      * @param xmlStr XML格式数据
310      * @param key API密钥
311      * @return 签名是否正确
312      * @throws Exception
313      */
314     public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
315         Map<String, String> data = xmlToMap(xmlStr);
316         if (!data.containsKey("sign") ) {
317             return false;
318         }
319         String sign = data.get("sign");
320         return generateSignature(data, key,SignType.MD5).equals(sign);
321     }
322
323
324
325
326
327 }            

3.小程序发起请求 组装发起统一下单所需要的参数

 1     @PostMapping("/recharge/wx")
 2     public Map recharge(HttpServletRequest request, @RequestParam(value = "vipType",required = true) VipType vipType) throws Exception {
 3         // 本案例是充值会员 用的时候根据实际情况改成自己的需求
 4         Integer loginDealerId = MySecurityUtil.getLoginDealerId();
 5         // 获取ip地址 发起统一下单必要的参数
 6         String ipAddress = HttpUtil.getIpAddress(request);
 7         // 生成预付订单 存入数据库 回调成功在对订单状态进行修改
 8         PrepaidOrder prepaidOrder = payService.recharge(loginDealerId, vipType, ipAddress);
 9         // 组装统一下单需要的数据map
10         Map<String, String> stringStringMap = prepaidOrder.toWxPayParameters();
11         // 调起统一支付
12         Map<String, String> payParameters =WxUtil.unifiedOrder(stringStringMap);
13         return payParameters;
14     }

生成预付订单代码(根据实际需求生成,此处只是我这的需求,仅供参考)

27 @Service("WXPayService")
28 @Slf4j
29 public class PayServiceImpl implements PayService {
30
33     @Resource
34     PrepaidOrderDao prepaidOrderDao;
35
36     @Resource
37     VipDao vipDao;
38
39     @Resource
40     DealerDao dealerDao;
41
42     @Resource
43     ApplicationContext applicationContext;
44     @Override
45     @Transactional
46     public PrepaidOrder recharge(Integer dealerId, VipType vipType, String userIp) {
47         Dealer dealer = dealerDao.getDealerById(dealerId);
48         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
49         String newDate = sdf.format(new Date());
50         Random random = new Random();
51         String orderNumber = newDate + random.nextInt(1000000);
52         BigDecimal amount = null;
53         // 如果不是生产环境  付一分钱
54         if (!applicationContext.getEnvironment().getActiveProfiles()[0].contains("prod")){
55             amount = BigDecimal.valueOf(0.01);
56         }else if (vipType.equals(VipType.YEAR)){
57             amount= BigDecimal.valueOf(999);
58         }else {
59             amount = BigDecimal.valueOf(365);
60         }
61         PrepaidOrder prepaidOrder = new PrepaidOrder();
62         prepaidOrder.setDealerId(dealerId);
63         prepaidOrder.setOpenId(dealer.getOpenId()); // 这个是微信需要的 openid
64         prepaidOrder.setVipType(vipType);
65         prepaidOrder.setUserIp(userIp);  // 这个是微信需要的参数 userIp
66         prepaidOrder.setOrderStatus(OrderStatus.ONGOING);
67         prepaidOrder.setAmount(amount); // 这个是微信需要的参数 total_fee
68         prepaidOrder.setOrderNumber(orderNumber); // 这个是微信需要的参数   out_trade_no
69         // 添加预付订单
70         prepaidOrderDao.addPrepaidOrder(prepaidOrder);
71         return prepaidOrder;// 返回预付订单
72  } 73 }

在实体类做最后的参数封装

 1 @Data
 2 public class PrepaidOrder extends BaseModel {
 3     private String orderNumber;
 4     private Integer dealerId;
 5     private Integer versionNum;
 6     private BigDecimal amount;
 7     private OrderStatus orderStatus=OrderStatus.ONGOING;
 8     private LocalDateTime successTime;
 9     private String userIp;
10     private String openId;
11     private VipType vipType;
12
13     public Map<String, String> toWxPayParameters() throws Exception {
14         Map map = new HashMap();
15         map.put("body",getBody());  // 商品名字
16         map.put("appid", WxConfig.appId); // 小程序appid
17         map.put("mch_id", WxConfig.mchId); // 商户id
18         map.put("nonce_str", WxUtil.generateNonceStr()); // 随机字符串
19         map.put("notify_url", AppConst.host+WxConfig.notifyPath); // 回调地址
20         map.put("openid",this.openId); // 发起微信支付的用户的openid
21         map.put("out_trade_no",this.orderNumber); // 订单号
22         map.put("spbill_create_ip",this.userIp); // 发起微信支付的用户的ip地址
23         map.put("total_fee",parseAmount()); // 金额 (单位分)
24         map.put("trade_type",WxConfig.tradeType); // 支付类型
25         // 数据签名   也是第一次签名
26         map.put("sign", WxUtil.generateSignature(map, WxConfig.key, SignType.MD5 ));
27         return map;
28     }
29
30     public String getBody(){
31         if (vipType.equals(VipType.YEAR)){
32             return "年度会员";
33         }else {
34             return "季度会员";
35         }
36     }
37
38     public String parseAmount(){
39         BigDecimal multiply = amount.multiply(BigDecimal.valueOf(100));
40         BigDecimal result = multiply;
41         if (multiply.compareTo(BigDecimal.valueOf(1))==0){
42             result = BigDecimal.valueOf(1);
43         }
44         return result.toString();
45     }
46
47     @Override
48     public String toString() {
49         return "PrepaidOrder{" +
50                 "orderNumber=‘" + orderNumber + ‘\‘‘ +
51                 ", dealerId=" + dealerId +
52                 ", versionNum=" + versionNum +
53                 ", amount=" + amount +
54                 ", orderStatus=" + orderStatus +
55                 ", successTime=" + successTime +
56                 ", userIp=‘" + userIp + ‘\‘‘ +
57                 ", openId=‘" + openId + ‘\‘‘ +
58                 ", vipType=" + vipType +
59                 ‘}‘;
60     }
61 }

4.签名类型的枚举类  public enum SignType { MD5, HMACSHA256 }

5.获取用户IP工具类

 1     public static String getIpAddress(HttpServletRequest request) {
 2         String ip = request.getHeader("x-forwarded-for");
 3         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
 4             ip = request.getHeader("Proxy-Client-IP");
 5         }
 6         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
 7             ip = request.getHeader("WL-Proxy-Client-IP");
 8         }
 9         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
10             ip = request.getHeader("HTTP_CLIENT_IP");
11         }
12         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
13             ip = request.getHeader("HTTP_X_FORWARDED_FOR");
14         }
15         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
16             ip = request.getRemoteAddr();
17         }
18         return ip;
19     }

本文阅读代码顺序  只针对本文

小程序发起微信支付->controller获取用户必要信息->service生成预付订单->实体类参数封装->WxUtil发起统一下单->返回结果

以上就是发起统一下单的所有流程了 整个流程

原文地址:https://www.cnblogs.com/LKiss/p/11980072.html

时间: 2024-11-09 03:21:04

springboot+微信小程序实现微信支付【统一下单】的相关文章

【转】微信小程序实现微信支付功能(可用)

原博: https://blog.csdn.net/fredrik/article/details/79697963 微信小程序实现微信支付功能 直接把里面的参数替换成你的就可以了,前提是你要开通的有微信支付功能,需要商户号,appid,appsecret,openid, //小程序端代码: pay:function(){ var that=this wx.getStorage({ key: 'openid', success: function(res) { wx.request({ //这里

微信小程序(原名微信应用号)开发工具0.9版安装教程

微信小程序全称微信公众平台·小程序,原名微信公众平台·应用号(简称微信应用号) 声明 微信小程序开发工具类似于一个轻量级的IDE集成开发环境,目前仅开放给了少部分受微信官方邀请的人士(据说仅200个名额)进行内测,因此目前未受到邀请的人士只能使用破解版: 本破解版资源来自于网上,与本人无关,仅供技术开发人员研究之用: 由于尚属内测阶段,因此迭代更新非常快,后续很可能由于升级而导致暂时无法使用.   特别注意 由于目前发布的0.9版本必须验证才能登录(估计是为了验证是否为内测人士),因此必须先下载

微信小程序_微信小程序开发,小程序源码、案例、教程

原文地址:http://whosmall.com/?post=448 本文标签: 微信小程序 小程序源码案例 小程序项目 小程序源码 微信小程序教程 什么是微信小程序? 微信小程序是微信基于微信平台的一个应用发布平台,微信小程序app开发属于原生app组件提供js接口的开发方式,比混合是app的用户体验更好,仅次于原生应用. 不过微信小程序定位于小,要符合轻量易用无需下载,所以从体积上也是有限制,整个小程序应用体积不能超过1M. 微信小程序的应用场景? 微信小程序的应用场景适用于轻量应用,非强交

微信小程序(微信应用号)开发ide安装解决方法

这两天整个技术圈都炸锅了,微信小程序(微信应用号)发布内测,首批200家收到邀请,但是没受邀请的同学,也不用担心,下面介绍一下解决方法. 首先需要下载ide,昨天只需要下载0.9版本的编辑器并替换文件就行了,但是可能微信那边修复了,导致不可用.现在我们要准备两个版本:0.7盒0.9的版本,我测试过了,可以使用,正常的界面如下图: 首先准备好两个安装文件,先安装0.7的版本.安装成功后,打开,微信扫描,确认登陆. 两个文件在QQ微信应用号群(390289365)里有共享. 然后安装0.9的版本,安

微信小程序与微信公众号同一用户登录问题

最近在做微信小程序与微信公众号登录合并的接口.整理相关资料以及个人认识的心得写了这篇文章与大家一起分享. 首先,简单说下我遇到的问题是我们的程序调用微信小程序得到openid,然后通过openID得到用户的唯一标识,用户得以登录,然而,当我们调用微信公众号也同样的到openid,同一以用户两个不同的openid,不能区分是否为同一用户,然后发现无论调用微信小程序还是微信公众号同一个用户的到unionid是相同的,所以我们就用unionid来区分是否为同一用户. UnionID机制说明: 如果开发

微信小程序、微信公众号、H5之间相互跳转

转自慕课网 一.小程序和公众号 答案是:可以相互关联. 在微信公众号里可以添加小程序. 图片有点小,我把文字打出来吧: 可关联已有的小程序或快速创建小程序.已关联的小程序可被使用在自定义菜单和模版消息等场景中. 公众号可关联同主体的10个小程序及不同主体的3个小程序.同一个小程序可关联最多50个公众号. 1.公众号跳小程序 比如说 "丰巢快递柜" 公众号关联的小程序:丰巢寄快递. 2.小程序跳公众号 打开"丰巢寄快递",点击右上角的菜单选项,然后点击"关于

[转]微信小程序、微信公众号、H5之间相互跳转

本文转自:https://www.cnblogs.com/colorful-paopao1/p/8608609.html 转自慕课网 一.小程序和公众号 答案是:可以相互关联. 在微信公众号里可以添加小程序. 图片有点小,我把文字打出来吧: 可关联已有的小程序或快速创建小程序.已关联的小程序可被使用在自定义菜单和模版消息等场景中. 公众号可关联同主体的10个小程序及不同主体的3个小程序.同一个小程序可关联最多50个公众号. 1.公众号跳小程序 比如说 “丰巢快递柜” 公众号关联的小程序:丰巢寄快

微信小程序之微信登陆 —— 微信小程序教程系列(20)

简介: 微信登陆,在新建一个微信小程序Hello World项目的时候,就可以看到项目中出现了我们的微信头像,其实这个Hello World项目,就有一个简化版的微信登陆.只不过是,还没有写入到咱们自家的后台中而已. 新建一个Hello World项目,找到app.js文件打开,代码如下: app.js: App({ onLaunch: function () { //调用API从本地缓存中获取数据 var logs = wx.getStorageSync('logs') || [] logs.

使用Appium 测试微信小程序和微信公众号方法

由于腾讯系QQ.微信等都是基于腾讯自研X5内核,不是google原生webview,需要打开TBS内核Inspector调试功能才能用Chrome浏览器查看页面元素,并实现Appium自动化测试微信小程序和微信公众号. 前提条件Appium环境搭建,这里不多说了,可查阅Appium环境搭建文章. 因夜神等模拟器是intel的X86架构,很多app安装不了,比如微信.qq等(虽然说可以通过安装arm解释器来解决该问题,但是进行X5内核调试的话也会出现问题),建议最好是一台真实的手机来做. webv