GVKun编程网logo

微信支付,你想知道的一切都在这里(微信支付,你想知道的一切都在这里吗)

12

如果您对微信支付,你想知道的一切都在这里和微信支付,你想知道的一切都在这里吗感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解微信支付,你想知道的一切都在这里的各种细节,并对微信支付,你想知道的一

如果您对微信支付,你想知道的一切都在这里微信支付,你想知道的一切都在这里吗感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解微信支付,你想知道的一切都在这里的各种细节,并对微信支付,你想知道的一切都在这里吗进行深入的分析,此外还有关于2017 OSC 年终盛典最全日程公布,你想知道的都在这里、2019RT-Thread社区大事件回顾,你想知道的都在这里!、Apache Doris 声明 | 你们想知道的一切,都在这里了。、App服务化, 10倍增长,你想知道的都在这里了!的实用技巧。

本文目录一览:

微信支付,你想知道的一切都在这里(微信支付,你想知道的一切都在这里吗)

微信支付,你想知道的一切都在这里(微信支付,你想知道的一切都在这里吗)

无论是个人还是企业,业务变现,除了广告最好的方式就是支付收款。我们经常使用的微信支付如何快速完成技术对接呢?如何同时支持国内支付与境外支付呢?如何做跨城冗灾呢?干货多屁话少 ,接下来慢慢聊。

接入步骤

微信支付接入大概步骤如下:

  1. 获取支付接口 URL
  2. 构建请求参数
  3. 发起请求
  4. 唤起支付
  5. 支付异步通知处理

步骤一中获取支付接口 URL,需要考虑这几点

  1. 如何同时支持国内微信支付与境外微信支付
  2. 如何同时支持普通的商户模式以及服务商模式

步骤二中经常遇到的问题就是参数签名验证问题

  1. MD5 加密
  2. HMAC-SHA256 加密

步骤三中难点在于微信支付双向证书的处理

步骤四中预付订单二次签名异常以及唤起支付提示各种配置错误

步骤五中异步通知验证签名、订单重复通知以及敏感数据的解密问题

以上接入步骤中,你踩过那些坑呢?欢迎评论区分享交流

Talk is cheap. Show me the code

获取微信支付接口 URL

有人会说「这不很简单么」官方文档接口中就有提供。对你说的没错,那如何做到一套系统同时支持国内微信支付与境外微信支付,又如何做跨城冗灾方案呢?

微信域名

根据业务区域的不同微信提供了不同的域名来支持

  • api.mch.weixin.qq.com(建议接入点:中国国内)
  • api2.mch.weixin.qq.com(建议接入点:中国国内备用)
  • apihk.mch.weixin.qq.com(建议接入点:东南亚)
  • apius.mch.weixin.qq.com(建议接入点:其它)
  • api.mch.weixin.qq.com/sandboxnew(特殊:仿真测试)

聪明的你,不难就会想到枚举,具体跨城冗灾方案可以参考微信支付商户系统跨城冗灾升级指引

/**
 * <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p>
 *
 * <p>不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。 </p>
 *
 * <p>IJPay 交流群: 723992875</p>
 *
 * <p>Node.js 版: https://gitee.com/javen205/TNW</p>
 *
 * <p>微信支付可用域名枚举</p>
 *
 * @author Javen
 */
public enum WxDomain {
    /**
     * 中国国内
     */
    CHINA("https://api.mch.weixin.qq.com"),
    /**
     * 中国国内(备用域名)
     */
    CHINA2("https://api2.mch.weixin.qq.com"),
    /**
     * 东南亚
     */
    HK("https://apihk.mch.weixin.qq.com"),
    /**
     * 其它
     */
    US("https://apius.mch.weixin.qq.com");

    
    /**
     * 域名
     */
    private final String domain;

    WxDomain(String domain) {
        this.domain = domain;
    }

    public String getType() {
        return domain;
    }
}

至此获取微信支付接口 URL 已解决掉了核心问题。剩下的就是根据不同的支付方式来拼接具体的支付接口 URL。

微信支付常用接口

付款码支付、JSAPI 支付、Native 支付、App 支付、H5 支付、小程序支付、红包、企业付款、酒店押金、刷脸支付常用的支付方式以及支付工具不完全统计接口大概有 90+

package com.ijpay.wxpay.enums;

/**
 * <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p>
 *
 * <p>不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。 </p>
 *
 * <p>IJPay 交流群: 723992875</p>
 *
 * <p>Node.js 版: https://gitee.com/javen205/TNW</p>
 *
 * <p>微信支付接口枚举</p>
 *
 * @author Javen
 */
public enum WxApiType {
    /**
     * 沙箱环境
     */
    SAND_BOX_NEW("/sandboxnew"),
    /**
     * 获取沙箱环境验签秘钥
     */
    GET_SIGN_KEY("/sandboxnew/pay/getsignkey"),
    /**
     * 统一下单
     */
    UNIFIED_ORDER("/pay/unifiedorder"),
    /**
     * 提交付款码支付
     */
    MICRO_PAY("/pay/micropay"),
    /**
     * 查询订单
     */
    ORDER_QUERY("/pay/orderquery"),
    /**
     * 关闭订单
     */
    CLOSE_ORDER("/pay/closeorder"),
    /**
     * 撤销订单
     */
    REVERSE("/secapi/pay/reverse"),
    /**
     * 申请退款
     */
    REFUND("/secapi/pay/refund"),
    /**
     * 查询退款
     */
    REFUND_QUERY("/pay/refundquery"),
    /**
     * 下载对账单
     */
    DOWNLOAD_BILL("/pay/downloadbill"),
    /**
     * 下载资金对账单
     */
    DOWNLOAD_FUND_FLOW("/pay/downloadfundflow"),
    /**
     * 交易保障
     */
    REPORT("/payitil/report"),
    /**
     * 转换短链接
     */
    SHORT_URL("/tools/shorturl"),
    /**
     * 授权码查询 openId
     */
    AUTH_CODE_TO_OPENID("/tools/authcodetoopenid"),
    /**
     * 拉取订单评价数据
     */
    BATCH_QUERY_COMMENT("/billcommentsp/batchquerycomment"),
    /**
     * 企业付款
     */
    TRANSFER("/mmpaymkttransfers/promotion/transfers"),
    /**
     * 查询企业付款
     */
    GET_TRANSFER_INFO("/mmpaymkttransfers/gettransferinfo"),
    /**
     * 企业付款到银行卡
     */
    TRANSFER_BANK("/mmpaysptrans/pay_bank"),
    /**
     * 查询企业付款到银行卡
     */
    GET_TRANSFER_BANK_INFO("/mmpaysptrans/query_bank"),
    /**
     * 获取 RSA 加密公钥
     */
    GET_PUBLIC_KEY("/risk/getpublickey"),
    /**
     * 发放红包
     */
    SEND_RED_PACK("/mmpaymkttransfers/sendredpack"),
    /**
     * 发放裂变红包
     */
    SEND_GROUP_RED_PACK("/mmpaymkttransfers/sendgroupredpack"),
    /**
     * 查询红包记录
     */
    GET_HB_INFO("/mmpaymkttransfers/gethbinfo"),
    /**
     * 小程序发红包
     */
    SEND_MINI_PROGRAM_HB("/mmpaymkttransfers/sendminiprogramhb"),
    /**
     * 发放代金券
     */
    SEND_COUPON("/mmpaymkttransfers/send_coupon"),
    /**
     * 查询代金券批次
     */
    QUERY_COUPON_STOCK("/mmpaymkttransfers/query_coupon_stock"),
    /**
     * 查询代金券信息
     */
    QUERY_COUPONS_INFO("/mmpaymkttransfers/querycouponsinfo"),
    /**
     * 请求单次分账
     */
    PROFIT_SHARING("/secapi/pay/profitsharing"),
    /**
     * 请求多次分账
     */
    MULTI_PROFIT_SHARING("/secapi/pay/multiprofitsharing"),
    /**
     * 查询分账结果
     */
    PROFIT_SHARING_QUERY("/pay/profitsharingquery"),
    /**
     * 添加分账接收方
     */
    PROFITS_HARING_ADD_RECEIVER("/pay/profitsharingaddreceiver"),
    /**
     * 删除分账接收方
     */
    PROFIT_SHARING_REMOVE_RECEIVER("/pay/profitsharingremovereceiver"),
    /**
     * 完结分账
     */
    PROFIT_SHARING_FINISH("/secapi/pay/profitsharingfinish"),
    /**
     * 分账回退
     */
    PROFIT_SHARING_RETURN("/secapi/pay/profitsharingreturn"),
    /**
     * 分账回退结果查询
     */
    PROFIT_SHARING_RETURN_QUERY("/pay/profitsharingreturnquery"),
    /**
     * 支付押金(人脸支付)
     */
    DEPOSIT_FACE_PAY("/deposit/facepay"),
    /**
     * 支付押金(付款码支付)
     */
    DEPOSIT_MICRO_PAY("/deposit/micropay"),
    /**
     * 查询订单(押金)
     */
    DEPOSIT_ORDER_QUERY("/deposit/orderquery"),
    /**
     * 撤销订单(押金)
     */
    DEPOSIT_REVERSE("/deposit/reverse"),
    /**
     * 消费押金
     */
    DEPOSIT_CONSUME("/deposit/consume"),
    /**
     * 申请退款(押金)
     */
    DEPOSIT_REFUND("/deposit/refund"),
    /**
     * 查询退款(押金)
     */
    DEPOSIT_REFUND_QUERY("deposit/refundquery"),
    /**
     * 公众号纯签约
     */
    ENTRUST_WEB("/papay/entrustweb"),
    /**
     * 公众号纯签约(服务商模式)
     */
    PARTNER_ENTRUST_WEB("/papay/partner/entrustweb"),
    /**
     * APP纯签约
     */
    PRE_ENTRUST_WEB("/papay/preentrustweb"),
    /**
     * APP纯签约(服务商模式)
     */
    PARTNER_PRE_ENTRUST_WEB("/papay/partner/preentrustweb"),
    /**
     * H5纯签约
     */
    H5_ENTRUST_WEB("/papay/h5entrustweb"),
    /**
     * H5纯签约(服务商模式)
     */
    PARTNER_H5_ENTRUST_WEB("/papay/partner/h5entrustweb"),
    /**
     * 支付中签约
     */
    PAY_CONTRACT_ORDER("/pay/contractorder"),
    /**
     * 查询签约关系
     */
    QUERY_ENTRUST_CONTRACT("/papay/querycontract"),
    /**
     * 查询签约关系(服务商模式)
     */
    PARTNER_QUERY_ENTRUST_CONTRACT("/papay/partner/querycontract"),
    /**
     * 代扣申请扣款
     */
    PAP_PAY_APPLY("/pay/pappayapply"),
    /**
     * 代扣申请扣款(服务商模式)
     */
    PARTNER_PAP_PAY_APPLY("/pay/partner/pappayapply"),
    /**
     * 查询代扣订单
     */
    PAP_ORDER_QUERY("/pay/paporderquery"),
    /**
     * 查询代扣订单
     */
    PARTNER_PAP_ORDER_QUERY("/pay/partner/paporderquery"),
    /**
     * 代扣申请解约
     */
    DELETE_ENTRUST_CONTRACT("/papay/deletecontract"),
    /**
     * 代扣申请解约(服务商模式)
     */
    PARTNER_DELETE_ENTRUST_CONTRACT("/papay/partner/deletecontract"),
    /**
     * 刷脸支付
     */
    FACE_PAY("/pay/facepay"),
    /**
     * 查询刷脸支付订单
     */
    FACE_PAY_QUERY("/pay/facepayqueryy"),
    /**
     * 撤销刷脸支付订单
     */
    FACE_PAY_REVERSE("/secapi/pay/facepayreverse"),
    /**
     * 小微商户申请入驻
     */
    MICRO_SUBMIT("/applyment/micro/submit"),
    /**
     * 查询申请状态
     */
    GET_MICRO_SUBMIT_STATE("/applyment/micro/getstate"),
    /**
     * 提交升级申请
     */
    MICRO_SUBMIT_UPGRADE("/applyment/micro/submitupgrade"),
    /**
     * 查询升级申请单状态
     */
    GET_MICRO_UPGRADE_STATE("/applyment/micro/getupgradestate"),
    /**
     * 查询提现状态
     */
    QUERY_AUTO_WITH_DRAW_BY_DATE("/fund/queryautowithdrawbydate"),
    /**
     * 修改结算银行卡
     */
    MICRO_MODIFY_ARCHIVES("/applyment/micro/modifyarchives"),
    /**
     * 重新发起提现
     */
    RE_AUTO_WITH_DRAW_BY_DATE("/fund/reautowithdrawbydate"),
    /**
     * 修改联系信息
     */
    MICRO_MODIFY_CONTACT_INFO("/applyment/micro/modifycontactinfo"),
    /**
     * 小微商户关注功能配置
     */
    ADD_RECOMMEND_CONF("/secapi/mkt/addrecommendconf"),
    /**
     * 小微商户开发配置新增支付目录
     */
    ADD_SUB_DEV_CONFIG("/secapi/mch/addsubdevconfig"),
    /**
     * 小微商户开发配置查询
     */
    QUERY_SUB_DEV_CONFIG("/secapi/mch/querysubdevconfig");

    /**
     * 类型
     */
    private final String type;

    WxApiType(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }
}

获取完整 URL 方案

同时支持任意接口任意域名的切换,为跨城冗灾打下良好基础

	/**
     * 获取接口请求的 URL
     *
     * @param wxApiType {@link WxApiType} 支付 API 接口枚举
     * @return {@link String} 返回完整的接口请求URL
     */
    public static String getReqUrl(WxApiType wxApiType) {
        return getReqUrl(wxApiType, null, false);
    }

    /**
     * 获取接口请求的 URL
     *
     * @param wxApiType {@link WxApiType} 支付 API 接口枚举
     * @param isSandBox 是否是沙箱环境
     * @return {@link String} 返回完整的接口请求URL
     */
    public static String getReqUrl(WxApiType wxApiType, boolean isSandBox) {
        return getReqUrl(wxApiType, null, isSandBox);
    }

    /**
     * 获取接口请求的 URL
     *
     * @param wxApiType {@link WxApiType} 支付 API 接口枚举
     * @param wxDomain  {@link WxDomain} 支付 API 接口域名枚举
     * @param isSandBox 是否是沙箱环境
     * @return {@link String} 返回完整的接口请求URL
     */
    public static String getReqUrl(WxApiType wxApiType, WxDomain wxDomain, boolean isSandBox) {
        if (wxDomain == null) {
            wxDomain = WxDomain.CHINA;
        }
        return wxDomain.getType()
                .concat(isSandBox ? WxApiType.SAND_BOX_NEW.getType() : "")
                .concat(wxApiType.getType());
    }

构建请求参数

Model 构建实现机制

这里构建请求参数使用的是 Lombok + Java 反射机制来实现。

封装 Model 自动生成签名

BaseModel 实现将 Lombok builder 后对象中的属性以及值转为 Map 并提供创建签名的方法自动生成 sign (同时支持 MD5 以及 HMAC-SHA256)。以微信支付中的统一下单为例代码如下

public class BaseModel {

    /**
     * 将建构的 builder 转为 Map
     *
     * @return 转化后的 Map
     */
    public Map<String, String> toMap() {
        String[] fieldNames = getFiledNames(this);
        HashMap<String, String> map = new HashMap<String, String>(fieldNames.length);
        for (int i = 0; i < fieldNames.length; i++) {
            String name = fieldNames[i];
            String value = (String) getFieldValueByName(name, this);
            if (StrUtil.isNotEmpty(value)) {
                map.put(name, value);
            }
        }
        return map;
    }

    /**
     * 构建签名 Map
     *
     * @param partnerKey API KEY
     * @param signType   {@link SignType} 签名类型
     * @return 构建签名后的 Map
     */
    public Map<String, String> createSign(String partnerKey, SignType signType) {
        return createSign(partnerKey,signType,true);
    }

    /**
     * 构建签名 Map
     *
     * @param partnerKey   API KEY
     * @param signType     {@link SignType} 签名类型
     * @param haveSignType 签名是否包含 sign_type 字段
     * @return 构建签名后的 Map
     */
    public Map<String, String> createSign(String partnerKey, SignType signType, boolean haveSignType) {
        return WxPayKit.buildSign(toMap(), partnerKey, signType,haveSignType);
    }

    /**
     * 获取属性名数组
     *
     * @param obj 对象
     * @return 返回对象属性名数组
     */
    public String[] getFiledNames(Object obj) {
        Field[] fields = obj.getClass().getDeclaredFields();
        String[] fieldNames = new String[fields.length];
        for (int i = 0; i < fields.length; i++) {
            fieldNames[i] = fields[i].getName();
        }
        return fieldNames;
    }

    /**
     * 根据属性名获取属性值
     *
     * @param fieldName 属性名称
     * @param obj       对象
     * @return 返回对应属性的值
     */
    public Object getFieldValueByName(String fieldName, Object obj) {
        try {
            String firstLetter = fieldName.substring(0, 1).toUpperCase();
            String getter = new StringBuffer().append("get")
                    .append(firstLetter)
                    .append(fieldName.substring(1))
                    .toString();
            Method method = obj.getClass().getMethod(getter, new Class[]{});
            return method.invoke(obj, new Object[]{});
        } catch (Exception e) {
            return null;
        }
    }

}

UnifiedOrderModel 微信统一下单

/**
 * <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p>
 *
 * <p>不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。 </p>
 *
 * <p>IJPay 交流群: 723992875</p>
 *
 * <p>Node.js 版: https://gitee.com/javen205/TNW</p>
 *
 * <p>统一下单 Model</p>
 *
 * @author Javen
 */
package com.ijpay.wxpay.model;

import com.ijpay.core.model.BaseModel;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class UnifiedOrderModel extends BaseModel {
    private String appid;
    private String mch_id;
    private String sub_appid;
    private String sub_mch_id;
    private String device_info;
    private String nonce_str;
    private String sign;
    private String sign_type;
    private String body;
    private String detail;
    private String attach;
    private String out_trade_no;
    private String fee_type;
    private String total_fee;
    private String spbill_create_ip;
    private String time_start;
    private String time_expire;
    private String goods_tag;
    private String notify_url;
    private String trade_type;
    private String product_id;
    private String limit_pay;
    private String openid;
    private String sub_openid;
    private String receipt;
    private String scene_info;
}

UnifiedOrderModel 中是熟悉字段完全来自官方接口文档。遗憾的是 Model 不能遵守小驼峰式命名规则。

签名算法实现

MD5 以及 HMAC-SHA256 签名算法实现使用的是 Hutool 提供的工具类来实现

	public static String hmacSha256(String data, String key) {
        return SecureUtil.hmac(HmacAlgorithm.HmacSHA256, key).digestHex(data, CharsetUtil.UTF_8);
    }

    public static String md5(String data) {
        return SecureUtil.md5(data);
    }

构建签名逻辑如下

	/**
     * 构建签名
     *
     * @param params     需要签名的参数
     * @param partnerKey 密钥
     * @param signType   签名类型
     * @return 签名后的 Map
     */
    public static Map<String, String> buildSign(Map<String, String> params, String partnerKey, SignType signType) {
        return buildSign(params,partnerKey,signType,true);
    }

    /**
     * 构建签名
     *
     * @param params       需要签名的参数
     * @param partnerKey   密钥
     * @param signType     签名类型
     * @param haveSignType 签名是否包含 sign_type 字段
     * @return 签名后的 Map
     */
    public static Map<String, String> buildSign(Map<String, String> params, String partnerKey, SignType signType, boolean haveSignType) {
        if(haveSignType){
            params.put(FIELD_SIGN_TYPE, signType.getType());
        }
        String sign = createSign(params, partnerKey, signType);
        params.put(FIELD_SIGN, sign);
        return params;
    }

	/**
     * 生成签名
     *
     * @param params     需要签名的参数
     * @param partnerKey 密钥
     * @param signType   签名类型
     * @return 签名后的数据
     */
    public static String createSign(Map<String, String> params, String partnerKey, SignType signType) {
        if (signType == null) {
            signType = SignType.MD5;
        }
        // 生成签名前先去除sign
        params.remove(FIELD_SIGN);
        String tempStr = PayKit.createLinkString(params);
        String stringSignTemp = tempStr + "&key=" + partnerKey;
        if (signType == SignType.MD5) {
            return md5(stringSignTemp).toUpperCase();
        } else {
            return hmacSha256(stringSignTemp, partnerKey).toUpperCase();
        }
    }

通过 Model 构建 xml 数据

通过上面的封装后不到 20 行代码就可以通过 Model 构建出微信支付接口所需要的 xml 数据

        WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();

        Map<String, String> params = UnifiedOrderModel
                .builder()
                .appid(wxPayApiConfig.getAppId())
                .mch_id(wxPayApiConfig.getMchId())
                .nonce_str(WxPayKit.generateStr())
                .body("IJPay 让支付触手可及-公众号支付")
                .attach("Node.js 版:https://gitee.com/javen205/TNW")
                .out_trade_no(WxPayKit.generateStr())
                .total_fee("1000")
                .spbill_create_ip(ip)
                .notify_url(notifyUrl)
                .trade_type(TradeType.JSAPI.getTradeType())
                .openid(openId)
                .build()
                .createSign(wxPayApiConfig.getPartnerKey(), SignType.HMACSHA256);
        String xml = WxPayKit.toXml(params);
        log.info(xml);

发起请求

由于篇幅原因这里不做详细介绍,请参考 扩展 Http 请求

唤起支付

这里常见的问题就是预付订单二次签名异常以及唤起支付提示各种配置错误,比如授权目录没有配置

预付订单二次签名封装

	/**
     * <p>公众号支付-预付订单再次签名</p>
     * <p>注意此处签名方式需与统一下单的签名类型一致</p>
     *
     * @param prepayId   预付订单号
     * @param appId      应用编号
     * @param partnerKey API Key
     * @param signType   签名方式
     * @return 再次签名后的 Map
     */
    public static Map<String, String> prepayIdCreateSign(String prepayId, String appId, String partnerKey, SignType signType) {
        Map<String, String> packageParams = new HashMap<String, String>(6);
        packageParams.put("appId", appId);
        packageParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
        packageParams.put("nonceStr", String.valueOf(System.currentTimeMillis()));
        packageParams.put("package", "prepay_id=" + prepayId);
        if (signType == null) {
            signType = SignType.MD5;
        }
        packageParams.put("signType", signType.getType());
        String packageSign = WxPayKit.createSign(packageParams, partnerKey, signType);
        packageParams.put("paySign", packageSign);
        return packageParams;
    }

    /**
     * <p>APP 支付-预付订单再次签名</p>
     * <p>注意此处签名方式需与统一下单的签名类型一致</p>
     *
     * @param appId      应用编号
     * @param partnerId  商户号
     * @param prepayId   预付订单号
     * @param partnerKey API Key
     * @param signType   签名方式
     * @return 再次签名后的 Map
     */
    public static Map<String, String> appPrepayIdCreateSign(String appId, String partnerId, String prepayId, String partnerKey, SignType signType) {
        Map<String, String> packageParams = new HashMap<String, String>(8);
        packageParams.put("appid", appId);
        packageParams.put("partnerid", partnerId);
        packageParams.put("prepayid", prepayId);
        packageParams.put("package", "Sign=WXPay");
        packageParams.put("noncestr", String.valueOf(System.currentTimeMillis()));
        packageParams.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        if (signType == null) {
            signType = SignType.MD5;
        }
        String packageSign = createSign(packageParams, partnerKey, signType);
        packageParams.put("sign", packageSign);
        return packageParams;
    }

    /**
     * <p>小程序-预付订单再次签名</p>
     * <p>注意此处签名方式需与统一下单的签名类型一致</p>
     *
     * @param appId      应用编号
     * @param prepayId   预付订单号
     * @param partnerKey API Key
     * @param signType   签名方式
     * @return 再次签名后的 Map
     */
    public static Map<String, String> miniAppPrepayIdCreateSign(String appId, String prepayId, String partnerKey, SignType signType) {
        Map<String, String> packageParams = new HashMap<String, String>(6);
        packageParams.put("appId", appId);
        packageParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
        packageParams.put("nonceStr", String.valueOf(System.currentTimeMillis()));
        packageParams.put("package", "prepay_id=" + prepayId);
        if (signType == null) {
            signType = SignType.MD5;
        }
        packageParams.put("signType", signType.getType());
        String packageSign = createSign(packageParams, partnerKey, signType);
        packageParams.put("paySign", packageSign);
        return packageParams;
    }

具体使用案例请参考 IJPay-Demo-SpringBoot

支付异步通知

目前微信支付异步通知有两种:支付结果异步通知、微信退款异步通知

注意事项: 1、及时响应对应的应答 2、异步通知需要根据订单号做去重处理 3、支付结果的异步通知签名方法必须与统一下单的签名方式保持一致 4、微信退款异步通知出现 java.security.InvalidKeyException: Illegal key size 异常 解决方案

验证签名封装

/**
     * 支付异步通知时校验 sign
     *
     * @param params     参数
     * @param partnerKey 支付密钥
     * @param signType   {@link SignType}
     * @return
     */
    public static boolean verifyNotify(Map<String, String> params, String partnerKey, SignType signType) {
        String sign = params.get("sign");
        String localSign = createSign(params, partnerKey, signType);
        return sign.equals(localSign);
    }

微信支付结果异步通知业务处理逻辑的伪代码

	/**
     * 异步通知
     */
    @RequestMapping(value = "/payNotify", method = {RequestMethod.POST, RequestMethod.GET})
    @ResponseBody
    public String payNotify(HttpServletRequest request) {
        String xmlMsg = HttpKit.readData(request);
        log.info("支付通知=" + xmlMsg);
        Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);

        String returnCode = params.get("return_code");

        // 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态
        // 注意此处签名方式需与统一下单的签名类型一致
        if (WxPayKit.verifyNotify(params, WxPayApiConfigKit.getWxPayApiConfig().getPartnerKey(), SignType.HMACSHA256)) {
            if (WxPayKit.codeIsOk(returnCode)) {
                // 更新订单信息
                // 发送通知等
                Map<String, String> xml = new HashMap<String, String>(2);
                xml.put("return_code", "SUCCESS");
                xml.put("return_msg", "OK");
                return WxPayKit.toXml(xml);
            }
        }
        return null;
    }

微信退款数据解密

微信退款数据解密详细步骤请参考官方文档,以下是使用 Hutool 提供 SecureUtil 实现

	/**
     * AES 解密
     *
     * @param base64Data 需要解密的数据
     * @param key        密钥
     * @return 解密后的数据
     */
    public static String decryptData(String base64Data, String key) {
        return SecureUtil.aes(md5(key).toLowerCase().getBytes()).decryptStr(base64Data);
    }

微信退款通知业务处理逻辑的伪代码

	/**
     * 退款通知
     */
    @RequestMapping(value = "/refundNotify", method = {RequestMethod.POST, RequestMethod.GET})
    @ResponseBody
    public String refundNotify(HttpServletRequest request) {
        String xmlMsg = HttpKit.readData(request);
        log.info("退款通知=" + xmlMsg);
        Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);

        String returnCode = params.get("return_code");
        // 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态
        if (WxPayKit.codeIsOk(returnCode)) {
            String reqInfo = params.get("req_info");
            String decryptData = WxPayKit.decryptData(reqInfo, WxPayApiConfigKit.getWxPayApiConfig().getPartnerKey());
            log.info("退款通知解密后的数据=" + decryptData);
            // 更新订单信息
            // 发送通知等
            Map<String, String> xml = new HashMap<String, String>(2);
            xml.put("return_code", "SUCCESS");
            xml.put("return_msg", "OK");
            return WxPayKit.toXml(xml);
        }
        return null;
    }

个人能力有限如有错误欢迎指正,如有遗漏欢迎补充。如有疑问欢迎留言一起交流讨论。

2017 OSC 年终盛典最全日程公布,你想知道的都在这里

2017 OSC 年终盛典最全日程公布,你想知道的都在这里

2017年OSC源创会年终盛典将于12月23号在北京举办,又到了与各位OSCer相约北京的时刻,大会报名一周倒计时,五人以上购票有团购特惠。(大会购票链接,最后一周售票,团购特惠,预购从速!)

作为开源中国一年一度举办的开发者盛会,年终盛典的主题,为大家从今年以来开源软件技术的众多主题中选取出来的精华,为开发者打造一场酣畅淋漓的纯技术分享盛典。除了上午主会场共探本年度开源焦点,更有下午四大分会场:综合技术分会场、PostgreSQL分会场、码云群英会、视频2046分会场,带您领略不一般的开源技术之美,大会最终版日程如下:

上午主会场:9:00-11:45

下午四大分会场:

1.综合技术分会场:

2.PostgreSQL分会场

3.视频2046分会场:

4.码云群英会:

距离大会举办时间邻近,还未购票的朋友们,赶紧行动起来吧,我们北京年终盛典见,购票请戳<<立即抢票!

 

2019RT-Thread社区大事件回顾,你想知道的都在这里!

2019RT-Thread社区大事件回顾,你想知道的都在这里!


曾经遥不可及的2020

现已近在咫尺

2019,RT-Thread社区里都发生了些什么呢?

今天,让我们一起来回忆一下吧~



RT-Thread

社区事件回顾


1月14日 RT-Thread发布新版在线文档中心,进一步优化学习体验

1月22日

《RT-Thread内核实现与应用开发实战指南》正式出版

1月29日 RT-Thread 3.1.2发布


01月

02月



2月1日 RT-Thread 4.0.0代码发布

2月18日 RT-Thread发布IoT传感器徽标计划 


线下培训走进

长沙、杭州、桂林、广州


3月11日 RT-Thread上线软件包展示平台  

 

3月19日《嵌入式实时操作系统——RT-Thread设计与实现》正式出版    


3月26日 RT-Thread启动嵌入式软件人才计划及开发者能力认证     

3月29日 STM32通用Bootloader 发布   

03月

04月


线下培训走进

深圳 、成都、上海、郑州、济南、天津、北京


4月26日 ST正式宣布RT-Thread成为意法半导体STM32战略合作伙伴     


线下培训走进

武汉、合肥

5月14日 RT-Thread人才计划官网上线     

5月16日 RT-Thread 4.0.1代码发布    

05月

06月


线下培训走进

西安

6月2日 RT-Thread能力认证的第一场考试举行    

6月4日 RT-Thread大学计划发布    

6月12日 RT-Thread 3.1.3发布   


7月4日 DIY智能战车集结令发布    

7月10 DIY基于RT-Thread的智能家居系统线上活动发布

7月19日 AT Device 发布    

7月27日 RT-Thread师资研讨会举行  

7月31日 RT-Thread MicroPython 开发环境发布    

07月

08月




8月18日 第14届研电赛 RT-Thread企业专项奖颁布

8月29日 RT-Thread & NXP 发布 i.MX RT 系列 BSP 新框架   


8月31日 深圳站开发者聚会举行

 




9月4日    发布W601 WIFI SOC 物联网开发套件     

09月

10月


线下培训走进

武汉、上海、深圳、西安、郑州 

10月10日 RT-Thread软件包应用开发赛举行    

10月14日 PPP Device发布

10月20日 2019年第二届全国大学生嵌入式芯片与系统设计竞赛RT-Thread企业特别奖颁布

10月24日 设立社区工作小组    



线下培训走进

南京、苏州、北京、青岛、沈阳、天津、重庆

11月23日 成都开发者大会举行   

11月15日  软件包网站更新

11月19日 RT-Thread跃居GitHub RTOS STAR排行榜第一   

11月21日  RT-Thread MicroPython IDE 正式版发布    


11月

12月

12月07日 上海开发者大会举行  

12月21日 深圳开发者大会举行

12月21日RT-Thread Studio 正式发布  

12月02日 RT-Thread Nano 3.1.3 正式发布    

12月08日 RT-Thread能力认证第二场考试举行   


最后

祝愿大家

2020,“鼠”你好运

(●''◡''●)


    RT-Thread最新发布

RT-Thread Studio:RT-Thread 官方团队历经一年用心打磨,推出了 RT-Thread Studio 集成开发环境(IDE),让大家告别 ENV,能够基于一款 IDE 快速的进行 RT-Thread 项目开发。

下载地址:https://www.rt-thread.org/page/studio.html

(请将以上链接复制至外部链接打开)


你可以添加微信17775982065为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群!





RT-Thread


让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。





长按二维码,关注我们



点击“阅读原文”进入RT-Thread官网

本文分享自微信公众号 - RTThread物联网操作系统(RTThread)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

Apache Doris 声明 | 你们想知道的一切,都在这里了。

Apache Doris 声明 | 你们想知道的一切,都在这里了。

相信这两天很多社区小伙伴都看到 StarRocks 所谓” 开源 “的动态了,开源用户群里有很多小伙伴在讨论,也有很多关心 Apache Doris 的朋友来问我们,诸如 “如何看待 StarRocks ‘开源 ''”、” Apache Doris 跟 StarRocks 是什么关系 “、” 社区分化的原因是什么 “、“为什么 StarRocks 不回馈给 Apache Doris ” 的问题。

作为 Apache Doris 主要维护团队,我们觉得有必要给大家澄清一些事情。

关于 Apache Doris 和 DorisDB、StarRocks 的关系

Apache Doris 的前世今生相信很多同学都有些许了解,之前在公众号里有过历史文章阐明关系,在 Apache Doris X Apache Pulsar 联合 Meetup 上也做过题为 “ Apache Doris 的过去、现在和未来 ” 的分享。

Doris 最早是解决百度凤巢统计报表的专用系统,随着百度业务的飞速发展对系统进行了多次迭代,逐渐承担起百度内部业务的统计报表和多维分析需求。2013 年,我们把 Doris 进行了 MPP 框架的升级,并将新系统命名为 Palo ,2017 年我们以百度 Palo 的名字在 GitHub 上进行了开源,2018 年贡献给 Apache 基金会时,由于与国外数据库厂商重名,因此选择用回最初的名字,这就是 Apache Doris 的由来。

那么 StarRocks 以及 DorisDB 是什么?

2020 年 2 月,百度 Doris 团队的个别同学离职创业,基于 Apache Doris 之前的版本做了自己的商业化闭源产品 DorisDB ,这就是 StarRocks 的前身。

关于社区分化的原因

按照 Apache License,基于开源产品进行商业化是被允许的。所以我们初期是希望能共同建设 Apache Doris 社区的,个人在职业上的选择与社区无关。在开源社区,每个人的社区身份都是被认可的。

后来我们发现,事情发展与我们的预期背道而驰。

比如 DorisDB 团队在对外宣传时,会宣称自己 “是 Apache Doris 的主创团队”、“ Apache Doris 的核心开发人员大部分在任职” 等诸类话术。

实际上, GitHub 上公开的数据显示,Apache Doris 贡献代码前三的 Contributor 全部在百度 Doris 团队就职,不知所谓的 “大部分” 和 “主创” 从何说起。

最近一年,提交 Commits 数量前二十的 Contributor 中,有一半来自百度 Doris 团队,另一半来自小米、美团、字节跳动、蜀海、网易等 Apache Doris 的开源用户,在此也对所有的 Contributors 表示由衷地感激。

而唯一一个 DorisDB 的 Contributor ,入职 DorisDB 时间为 2021 年 8 月 27 日。没错,入职 DorisDB 快两周了,之前在百度 Doris 团队。

实际上,从 2020 年初起, DorisDB 团队几乎没有向 Apache Doris 提交过一行代码少部分开发者原本是 Apache Doris 的 Contributor ,在加入 DorisDB 团队后,同样不再向 Apache Doris 贡献一行代码

比如 DorisDB 团队在人员扩张时,会故意定向挖 Apache Doris 企业用户的员工。开源社区的发展离不开用户的支持,挖用户墙角更无异于自掘坟墓。对于员工个人主动的选择我们不去评判,但这让企业用户对自己员工的培养做了嫁衣。而短视的人是不会看到这些的,更认为与他们毫无关系, Apache Doris 的死活与他们无关,只要自己能招到人就行。

比如 DorisDB 的商标问题,从品牌角度来说,开源项目与商业化产品的品牌必须存在区分度,比如 Linux 和 RedHat 、 Hadoop 与 Cloudera 、Apache Kylin 和 Kyligence 。

而 DorisDB 和 Apache Doris ,相信很多开源用户在初次接触 Doris 的时候都会迷惑这两个产品的区别是什么,甚至以为是同一个产品。这也是 DorisDB 的目的所在,品牌上的混淆可以带来用户流量,这就够了。而 Apache 基金会对此事件有过多次发声, DorisDB 及其团队不管不问,企图继续混淆视听,直到最后在 Apache 基金会的压力下,才不得不通过所谓的 “开源” 来更名。

比如所谓的 “致 Clickhouse 的一封信”。Apache Doris 与 Clickhouse 都是 MPP 数据库领域的优秀产品,各自擅长的领域或适用的场景存在差异,所有用户可以基于技术认知和业务需求来抉择到底该选择哪一款产品,甚至在大多场景里两者是可以并存和相互补足的。

Apache Doris 不会、也十分不认可,通过贬低 Clickhouse 来达到推广自己的目的,这与开源的精神十分不符。而 DorisDB 选择向 Clickhouse 开战的行为,也使 Apache Doris 承受了许多本不应该由我们承担的骂名和非议。

比如 Apache Doris 的向量化执行引擎,本来至少提前一个季度就可以与用户们见面。DorisDB 已经有接近两年没有参与过一次社区讨论,唯独在我们把向量化引擎的代码提交 PR 并发起 Veto 这一关键的时间点,给了唯一的 -1 。DorisDB 给 -1 的理由我想不言而喻,无非是为了自己的商业化利益来阻拦社区的关键发展。

尽管无意义的 -1 可以忽视,但我们仍遵守社区规范,这无疑带来了我们许多额外的工作量,也打乱了我们原定的发版节奏。不过幸好最晚 9 月中旬,我们自己的向量化引擎就会提交到社区了,欢迎所有小伙伴关注。

………

诸如此类的事情日积月累,我们明白其实社区的分化已经无可避免。作为 Apache Doris 的维护团队,我们其实不愿意面对这样的局面,但当少数人想要凌驾于社区规则之上并持续向社区吸血时,附骨之蛆不要也罢。

关于如何看待 StarRocks  “开源”

两个方面来看。

对于改名。

从 2021 年下半年开始,我们就在努力地筹备 Apache Doris 毕业的事宜,横在我们面前的阻碍,其中最重要的事情之一就是 DorisDB 对 Apache Doris 的品牌侵权问题。

因为他们最初将产品命名为 DorisDB 就受到了 Apache 基金会的质疑,进而阻碍了 Apache Doris 的毕业进程,也给 Apache Doris 社区带来了困扰。最终在 Apache 基金会的施压和我们的抗议下,不得已作出了改名的行为。

改名对我们来说,意味着 Branding 问题不复存在,意味着扫清了毕业路上最大的一个障碍,我们也会继续尽全力投入在 Apache Doris 的毕业筹备工作上。

对于 “开源”

注意 “开源” 这个地方打了引号,其实可以给大家科普一下开源许可协议的背景和差异。

开源促进组织 OSI (Open Source Initiative,也被译为开放源代码促进会,官网地址 https://opensource.org/ )是一个推动开源软件发展的非盈利组织。OSI 定义了近百种开源协议,这已经成为了开源协议的事实标准。换句话说,不被 OSI 认可就不是开源。

Apache License 2.0 作为最主流的开源协议,被 OSI 认定为 “受欢迎且被广泛使用或具有强大社区的许可证 “(The following OSI-approved licenses are popular, widely used, or have strong communities)。

有关 Apache License 2.0 的具体内容,可以在 Apache 官网(  http://www.apache.org/licenses/LICENSE-2.0 )查阅。简单来说,分发完全自由、允许项目代码被修改、允许作为开源或商业化软件再次发布,一旦授权永久有效,在修改代码或有源代码衍生的代码中需要带有原来代码的协议、专利声明等。这是对于任何商业化公司和开源用户都极其友好的协议,而 Apache Doris 作为 Apache 基金会的项目,遵守的就是 Apache License 2.0。

StarRocks 呢?遵守的是 Elastic License 2.0。

Elastic 和 AWS 的纷争我们不再去重提。Elastic 修改开源协议为 SSPL 和 Elastic License 双许可,其本意是保护自身原厂的权益,要求未对项目作出贡献的情况下不得发布自己的开源及商业化产品。StarRocks Fork 的是 Apache Doris ,这身份已经本末倒置了,请问 DorisDB 是否有回馈上游?这不仅不遵守基本的 Upstream First 原则,还声称要保护 StarRocks 的知识产权,这也是十分双标了。

再者,Elastic License 的内容如果有心去看,可以发现有 “不得将产品作为托管服务提供给其他人”、“不得规避许可证密钥功能或删除 / 隐藏受许可证密钥保护的功能”、“不能更改许可证” 等条件,简单来说就是,你想在云上用?不行!如果有些商业化功能我屏蔽了不让你用,你想不花钱就用?不行!你只能遵守我的协议,如果分发后想换成别的协议?不行。

种种限定的孰是孰非我们不去细究,至少 OSI 不认可 Elastic 协议,认定其是 “伪开源协议”,充其量叫 “源代码可获取”。不信?可以去看看 Apache Skywalking 是如何回应 Elasticsearch 修改开源协议的。

为什么 StarRocks 不回馈给 Apache Doris ?

说完了如何看待 StarRocks “开源”,再说下他们为什么不回馈上游的 Apache Doris。

我们之前一直是希望能够共同建设 Apache Doris 社区的,至少在他们所谓的 “开源” 之前,但是多次沟通没有任何结果。

至于 StarRocks “开源” 之后,那就更不可能了,因为 StarRocks 选择的 Elastic License 开源协议不被认可, StarRocks 的代码也无法被引入到任何 Apache 以及所有 OSI 认可的项目中,这包括了大数据领域几乎所有耳熟能详的项目,诸如 Hive、Spark、Flink、Pulsar、Kafka、Impala、Kylin、Clickhouse、Hudi 等等。换句话说,基本上已经被主流的开源大数据项目隔绝开来。当然,Apache Doris 也引入不了任何 StarRocks 的代码,这也是 StarRocks 选择 Elastic License 开源协议的出发点之一。

作为 Fork 自 Apache Doris 的项目,StarRocks 开出新的分支后不仅分裂了社区,而且不回馈上游,甚至还修改了开源协议、誓要与上游彻底决裂,这种所谓的 “开源” 行为本身就很违背开源精神。

诚然,法无禁止即可为。Apache License 上约束不了这样的恶意行为,但大多社区用户是知道的,真正理解开源的人也是知道的,到底有何是不可为的。

总结一下

OK,前面讲的东西比较多,大家消化消化,话题收敛。

谴责也好,澄清也好,抱怨也好,在既定的事实面前都没有任何作用,说点实际一些的。

这个时代不会阻止任何一朵星光闪耀,每个人都有自己的星辰大海。

只不过未来各自朝着各自的启明星和灯塔航行而已。

祝未来一切都好,毕竟 StarRocks 飞得再高,也是站在了巨人肩膀上。

话题回到 Apache Doris。

作为 Apache Doris 的创始和维护团队,我们一直坚持在做,也会持续去做,会更加开放地拥抱开源,不会因为任何人的任何行为改变自己的方向。

我们很清楚接下来该如何开展后续工作,将会持续在社区用户的支持、产品性能的增强以及功能边界的拓展上投入更多人力和精力。我们希望能帮助更多社区用户,解决数据分析的痛点与难题,这是 Apache Doris 开源之初的愿景,一路走来,从未改变。我们也希望,能让 Apache Doris 进一步完善,向世界顶级的分析型数据库产品之路上再进一步。

说几点我们最近在做的事情吧。

Apache Doris 向量化执行引擎, 9 月底会出第一版,预计在单表上性能会有数倍提升,年底向量化执行引擎就会全面推出,敬请期待。

Doris Manager 可视化运维监控平台,我们也会尽快推出并开源,让运维更便捷和简易。

还有很多事情都在之前的开发者会议上提到了,可以查看之前的开发者会议总结 社区活动| Apache Doris 社区开发者会议总结。

社区活动方面,不管是 SIG 、征文活动、开发者会议还是即将举办的 Meetup ,希望大家多多参与,希望每个人都能在社区获得成长、有更多收获。

最后,感谢陪伴。

以上。

App服务化, 10倍增长,你想知道的都在这里了!

App服务化, 10倍增长,你想知道的都在这里了!

Apple为iOS 9发布了一个所谓的通用链接的深层链接特性,即Universal links。虽然它并不完美,但是这一发布,让数以千计的应用开发人员突然意识到自己的应用体验被打破。
魔窗推出了mLink ,为了打破App孤岛格局,构建情景式的服务链,大幅提升用户体验,让每一次的连接都产生价值。

什么是Universal links?
Universal links,一种能够方便的通过传统的HTTP/HTTPS 链接来启动App,使用相同的网址打开网站和App。
试想一下,通过唯一的网址,一个用户点击可以链接到你网站的网址,当App已经被安装的时候,直接无缝的重定向到App中而不是打开 Safari,当用户没有安装App的时候,直接在Safari中打开链接,通过这个唯一的网址还可以链接到您App中某个特定的视图,而不是特别的URL Schemes。这种用户体验是不是相当的棒?

相较custom URL Schemes, Universal links的好处
(1)不同的App是可以定义相同的custom URL Schemes的,所以会存在抢占或者冲突的问题,而Universal links使用标准的HTTP/HTTPS,所以不存在上述问题。
(2)custom URL Schemes在没有安装App的情况下是无法直接打开的,而Universal links本身是一个HTTP/HTTPS 链接,所以在没有安装App的情况下,可以用Safari打开相应的webView 。
在微信中打开Universal link ,并且App未安装的情况下:
听萌妹纸说,如何在微信中直接唤醒第三方App

在微信中打开custom URL Schemes,并且 App 未安装的情况下:

(3)在 App 已安装的情况下,在微信中点击跳转链接,custom URL Schemes是无法直接打开App的,因为微信屏蔽了custom URL Schemes的跳转,而Universal links由于其本身是HTTP/HTTPS 链接的优势,可以跳出微信的屏蔽,直接从微信中跳转并打开相应的App。
在微信中打开Universal link ,并且App已安装的情况下:

在微信中打开customURL Schemes,并且App已安装的情况下:

(4)Universal links可以使用相同的网址打开网站和 App。

(5)Universal links支持从其他App的MKWebView或UIWebView中跳转到目标 App。
(6)Universal links本身可以被搜索引擎索引。
Universal links的具体实现
实现Universal links不难,但是也有一些门槛:
有一个注册的域名
通过 SSL 访问域名
支持上传一个JSON文件到你的域名
至少iOS 9+
至少Xcode 7 beta 2
(1)添加一个apple-app-site-association文件到你的网站来描述 URL 和 App 的关联。
该文件必须存在且为了安全原因需要使用SSL 通过 GET 请求访问到。
在你的apple-app-site-association文件中,是需要写一个简单的JSON格式,比如:
{
"applinks": {
"apps": [],
"details": [
{
"appID":"9JA89QQLNQ.com.apple.wwdc",
"paths": [ “/wwdc/news/",“/videos/wwdc/2015/*"]
},
{
"appID":"TeamID.BundleID2",
"paths": [ “*" ]
}
]
}
}
(2)添加Associated Domains来指定要从哪些域名查询Universal link support。在Xcode的capabilities 里添加你的App域名,必须用applinks前置它。
(3)在App delegate的application:continueUserActivity:restorationHandler: 方法中handleuniversal links

魔窗mLink对Universal links的支持
魔窗除了对custom URL Schemes之外,同时也对Universal links进行了支持。
如果App已经配置了Universal links ,只需要在魔窗后台添加相应的Universal links即可;
如果App没有配置Universal links ,那么只需要提供Team ID 和Bundle ID,魔窗为App提供Universal links服务。

我们今天的关于微信支付,你想知道的一切都在这里微信支付,你想知道的一切都在这里吗的分享已经告一段落,感谢您的关注,如果您想了解更多关于2017 OSC 年终盛典最全日程公布,你想知道的都在这里、2019RT-Thread社区大事件回顾,你想知道的都在这里!、Apache Doris 声明 | 你们想知道的一切,都在这里了。、App服务化, 10倍增长,你想知道的都在这里了!的相关信息,请在本站查询。

本文标签:

上一篇微信浏览器跳转页面加载 loading 效果问题(微信跳转浏览器打开)

下一篇JFinal 极速开发微信公众号(java公众号开发)