- 微信支付回调通知 解密前 如何确定商户的RSAAutoCertificateConfig
官方SDK - 回调通知验签和解密 // 构造 RequestParam RequestParam requestParam = new RequestParam.Builder() .serialNumber(wechatPayCertificateSerialNumber) .nonce(nonce) .signature(signature) .timestamp(timestamp) .body(requestBody) .build(); // 如果已经初始化了 RSAAutoCertificateConfig,可直接使用 // 没有的话,则构造一个 【*****请问这里 未解密前如果能知道是哪个商户号的回调??****】 NotificationConfig config = new RSAAutoCertificateConfig.Builder() .merchantId(merchantId) .privateKeyFromPath(privateKeyPath) .merchantSerialNumber(merchantSerialNumber) .apiV3Key(apiV3key) .build(); // 初始化 NotificationParser NotificationParser parser = new NotificationParser(config); // 以支付通知回调为例,验签、解密并转换成 Transaction Transaction transaction = parser.parse(requestParam, Transaction.class);
2023-07-15 - 回调通知验签和解密给的api是不是不太对?
[图片] NotificationParser 的构造方法中的参数是数组呀 [图片] 和示例中的也对不上呀 [图片]
2022-12-28 - 刚收到通知获取手机号收费开始了?
[图片] https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getRealtimePhoneNumber.html https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/paymentManage.html 未来:旧版本接口依然可以使用,只是需要收费而已,不想做改动的交钱就行了。如果想用新API方法就去改吧,多花一分钱。 可能支持的省钱办法: 授权手机号后,服务端将openId、手机号进行绑定。用户onLaunch打开小程序的时候通过wx.login获取code去解密openId,同时由于服务端已经绑定过手机号,所以可以使用该手机号进行登录,并同步返回token、jwtToken等登录态。这样可以做到用户冷启动小程序时自动登录上,减少使用授权的逻辑。业务按钮点击后 先调用wx.login,如果返回token则进行后续业务,如果没返回则弹出自定义弹窗,弹窗内点击按钮再进行手机号授权。(也可以在部分页面onLoad里wx.login),这个场景因为会延长流程,所以产品说不考虑,先直接打开页面就登录上,你们的各自看各自的业务场景吧。然后有四个疑问: 充值购买次数后会,如果小程序被封禁了,充值的金额是否可退款。购买数量是否支持按量付费?如果次数用完了,未购买新的次数,用户端的表现是什么?如果次数用完了,之前文档说的余量20%、10%、5%时会发模板消息提醒,文档相关现在已经删除了,是否还会发?[图片] ———————————————————————————————————————————————— 今天看了下文档做了改动: 退款规则:若购买有误,且未正式开始使用资源包前,可以在支付成功后的7天内申请退款。款项将在3-5个工作日内从原支付路径返回;若资源包已经开始使用(使用1次及以上),则不能申请退款;若支付成功后超过7天,未发起退款申请,亦不能再申请退款。 那么小程序被封了应该是不退的。不确定,等官方回复次数用完了,用户授权不会弹出授权弹窗,会返回一个errNo:1400001,用户判断等于这个errNo的时候跳转到自己的账密登录页面。不确定,等官方回复———————————————————————————————————————————————— 据了解老版本的快速验证组件(获取手机号),180天才会发送短信验证一次,为啥能每次授权都收费0.03元。 社区搜了一张图,180天没验证的应该会弹这个,不是说是短信运营成本么?为啥不是第180天验证那次费用让我们付,而是每次授权都付? [图片] 手机号授权改造后的效果: 打开职位详情页:优先调用接口判断openId是否绑定过。 如果未绑定:使用button的open-type=“getPhoneNumber”,点击报名弹出手机号授权,授权成功后与openId进行绑定落库。 如果已绑定,页面通过变量判断使用wx.login静默授权,同时服务端拿到绑定的手机号后进行登录操作,同步返回登录态(token/jwtToken)。 退出登录页面增加解绑操作(服务端解除openId与手机号的绑定),此时用户再次点击报名,就会弹出手机号授权,方便用户切换手机号。 [视频]
2023-07-27 - 微信:小程序获取手机号要开始收费了!
[图片] 收费说明 自2023年8月26日起,手机号实时验证组件将需要[代码]付费使用[代码]。 [图片] 手机号快速验证组件 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html 新版本组件不再需要提前调用wx.login进行登录。 代码示例 [代码]<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button> Page({ getPhoneNumber (e) { console.log(e.detail.code) } }) [代码] 返回参数说明 code,动态令牌。可通过动态令牌换取用户手机号。使用方法详情phonenumber.getPhoneNumber接口。 请注意: 目前该接口针对非个人开发者,且完成了认证的小程序开放(不包含海外主体); 该能力使用时,用户可选择绑定号码,或自主添加号码。平台会基于中国三大运营商提供的短信等底层能力对号码进行验证,但不保证是实时验证; 请开发者根据业务场景需要自行判断并选择是否使用,必要时可考虑增加其他安全验证手段。 开发者需合理使用,若被发现或用户举报开发者不合理地要求用户提供手机号等个人信息,中断了正常的使用流程,影响了用户的使用体验,微信有权依据《微信小程序平台运营管理规范》对该小程序进行处理。常见违规事例和具体解析; 自2023年8月26日起,手机号快速验证组件将需要付费使用。标准单价为:每次组件调用成功,收费0.03元。 手机号快速验证组件(旧版):https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/deprecatedGetPhoneNumber.html 注意 目前该接口针对非个人开发者,且完成了认证的小程序开放(不包含海外主体)。需谨慎使用,若用户举报较多或被发现在不必要场景下使用,微信有权永久回收该小程序的该接口权限。 该能力使用时,用户可选择绑定号码,或自主添加号码。平台会对号码进行验证,但不保证是实时验证; 请开发者根据业务场景需要自行判断并选择是否使用,必要时可考虑增加其他安全验证手段。 手机号实时验证组件 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getRealtimePhoneNumber.html 代码示例 [代码]<button open-type="getRealtimePhoneNumber" bindgetphonenumber="bindgetrealtimephonenumber"></button> Page({ getRealtimePhoneNumber (e) { console.log(e.detail.code) } }) [代码] 该能力与手机号快速验证组件的区别为: 手机号实时验证组件,在每次请求时,平台均会对用户选择的手机号进行实时验证; 手机号快速验证组件,平台会对号码进行验证,但不保证是实时验证。 请注意: 3. 目前该接口针对非个人开发者,且完成了认证的小程序开放(不包含海外主体); 4. 该能力使用时,用户可选择绑定号码,或自主添加号码。每次请求时,平台均会基于中国三大运营商提供的短信等底层能力对号码进行实时验证; 5. 请开发者根据业务场景需要自行判断并选择是否使用,必要时可考虑增加其他安全验证手段。 6. 开发者需合理使用,若用户举报或被发现开发者不合理地要求用户提供手机号等个人信息,中断了正常的使用流程,影响了用户的使用体验,微信有权依据《微信小程序平台运营管理规范》对该小程序进行处理。常见违规事例和具体解析; 7. 该能力的bindgetrealtimephonenumber 事件回调中,仅会返回 code,不会返回 encryptedData,开发者仅可通过消费 code的方式换取用户手机号; 自2023年8月26日起,手机号实时验证组件将需要付费使用。标准单价为:每次组件调用成功,收费0.04元。 付费管理 https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/paymentManage.html 修改建议 手机号快速验证组件旧版本接口依然可以使用,只是需要收费而已,不想做改动的交钱就行了。如果想用新接口就去改吧,多花一分钱(增强小程序安全性)。
2023-06-30 - 小程序用户头像昵称获取规则调整公告
更新时间:2022年11月9日由于 PC/macOS 平台「头像昵称填写能力」存在兼容性问题,对于来自低于2.27.1版本的访问,小程序通过 wx.getUserProfile 接口将正常返回用户头像昵称,插件通过 wx.getUserInfo 接口将正常返回用户头像昵称。 更新时间:2022年9月28日考虑到近期开发者对小程序用户头像昵称获取规则调整的相关反馈,平台将接口回收的截止时间由2022年10月25日延期至2022年11月8日24时。 调整背景在小程序内,开发者可以通过 wx.login 接口直接获取用户的 openId 与 unionId 信息,实现微信身份登录,支持开发者在多个小程序或其它应用间匿名关联同一用户。 同时,为了满足部分小程序业务中需要创建用户的昵称与头像的诉求,平台提供了 wx.getUserProfile 接口,支持在用户授权的前提下,快速使用自己的微信昵称头像。 但实践中发现有部分小程序,在用户刚打开小程序时就要求收集用户的微信昵称头像,或者在支付前等不合理路径上要求授权。如果用户拒绝授权,则无法使用小程序或相关功能。在已经获取用户的 openId 与 unionId 信息情况下,用户的微信昵称与头像并不是用户使用小程序的必要条件。为减少此类不合理的强迫授权情况,作出如下调整。 调整说明自 2022 年 10 月 25 日 24 时后(以下统称 “生效期” ),用户头像昵称获取规则将进行如下调整: 自生效期起,小程序 wx.getUserProfile 接口将被收回:生效期后发布的小程序新版本,通过 wx.getUserProfile 接口获取用户头像将统一返回默认灰色头像,昵称将统一返回 “微信用户”。生效期前发布的小程序版本不受影响,但如果要进行版本更新则需要进行适配。自生效期起,插件通过 wx.getUserInfo 接口获取用户昵称头像将被收回:生效期后发布的插件新版本,通过 wx.getUserInfo 接口获取用户头像将统一返回默认灰色头像,昵称将统一返回 “微信用户”。生效期前发布的插件版本不受影响,但如果要进行版本更新则需要进行适配。通过 wx.login 与 wx.getUserInfo 接口获取 openId、unionId 能力不受影响。「头像昵称填写能力」支持获取用户头像昵称:如业务需获取用户头像昵称,可以使用「头像昵称填写能力」(基础库 2.21.2 版本开始支持,覆盖iOS与安卓微信 8.0.16 以上版本),具体实践可见下方《最佳实践》。小程序 wx.getUserProfile 与插件 wx.getUserInfo 接口兼容基础库 2.27.1 以下版本的头像昵称获取需求:对于来自低版本的基础库与微信客户端的访问,小程序通过 wx.getUserProfile 接口将正常返回用户头像昵称,插件通过 wx.getUserInfo 接口将正常返回用户头像昵称,开发者可继续使用以上能力做向下兼容。对于上述 3,wx.getUserProfile 接口、wx.getUserInfo 接口、头像昵称填写能力的基础库版本支持能力详细对比见下表: [图片] *针对低版本基础库,兼容处理可参考 兼容文档 请已使用 wx.getUserProfile 接口的小程序开发者和已使用 wx.getUserInfo 接口的插件开发者尽快适配。小游戏不受本次调整影响。 最佳实践小程序可在个人中心或设置等页面使用头像昵称填写能力让用户完善个人资料: [图片] 微信团队 2022年5月9日
2023-09-26 - UserInfo.avatarUrl 字段是被删除了吗?
小程序点击登录无反应,经过查找sourceMap和日志发现报错如下:Cannot read properties of undefined (reading \'avatarUrl\')。 使用的是原来老的获取方式:open-type="getUserInfo" 此小程序已经两年没有更新过。所以比较奇怪是哪里出的问题。
2023-07-10 - 小程序登录、用户信息相关接口调整说明
公告更新时间:2021年04月15日考虑到近期开发者对小程序登录、用户信息相关接口调整的相关反馈,为优化开发者调整接口的体验,回收wx.getUserInfo接口可获取用户授权的个人信息能力的截止时间由2021年4月13日调整至2021年4月28日24时。为优化用户的使用体验,平台将进行以下调整: 2021年2月23日起,若小程序已在微信开放平台进行绑定,则通过wx.login接口获取的登录凭证可直接换取unionID2021年4月28日24时后发布的小程序新版本,无法通过wx.getUserInfo与<button open-type="getUserInfo"/>获取用户个人信息(头像、昵称、性别与地区),将直接获取匿名数据(包括userInfo与encryptedData中的用户个人信息),获取加密后的openID与unionID数据的能力不做调整。此前发布的小程序版本不受影响,但如果要进行版本更新则需要进行适配。新增getUserProfile接口(基础库2.10.4版本开始支持),可获取用户头像、昵称、性别及地区信息,开发者每次通过该接口获取用户个人信息均需用户确认。具体接口文档:《getUserProfile接口文档》由于getUserProfile接口从2.10.4版本基础库开始支持(覆盖微信7.0.9以上版本),考虑到开发者在低版本中有获取用户头像昵称的诉求,对于未支持getUserProfile的情况下,开发者可继续使用getUserInfo能力。开发者可参考getUserProfile接口文档中的示例代码进行适配。请使用了wx.getUserInfo接口或<button open-type="getUserInfo"/>的开发者尽快适配。开发者工具1.05.2103022版本开始支持getUserProfile接口调试,开发者可下载该版本进行改造。 小游戏不受本次调整影响。 一、调整背景很多开发者在打开小程序时就通过组件方式唤起getUserInfo弹窗,如果用户点击拒绝,无法使用小程序,这种做法打断了用户正常使用小程序的流程,同时也不利于小程序获取新用户。 二、调整说明通过wx.login接口获取的登录凭证可直接换取unionID 若小程序已在微信开放平台进行绑定,原wx.login接口获取的登录凭证若需换取unionID需满足以下条件: 如果开发者帐号下存在同主体的公众号,并且该用户已经关注了该公众号如果开发者帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用2月23日后,开发者调用wx.login获取的登录凭证可以直接换取unionID,无需满足以上条件。 回收wx.getUserInfo接口可获取用户个人信息能力 4月28日24时后发布的新版本小程序,开发者调用wx.getUserInfo或<button open-type="getUserInfo"/>将不再弹出弹窗,直接返回匿名的用户个人信息,获取加密后的openID、unionID数据的能力不做调整。 具体变化如下表: [图片] 即wx.getUserInfo接口的返回参数不变,但开发者获取的userInfo为匿名信息。 [图片] 此外,针对scope.userInfo将做如下调整: 若开发者调用wx.authorize接口请求scope.userInfo授权,用户侧不会触发授权弹框,直接返回授权成功若开发者调用wx.getSetting接口请求用户的授权状态,会直接读取到scope.userInfo为true新增getUserProfile接口 若开发者需要获取用户的个人信息(头像、昵称、性别与地区),可以通过wx.getUserProfile接口进行获取,该接口从基础库2.10.4版本开始支持,该接口只返回用户个人信息,不包含用户身份标识符。该接口中desc属性(声明获取用户个人信息后的用途)后续会展示在弹窗中,请开发者谨慎填写。开发者每次通过该接口获取用户个人信息均需用户确认,请开发者妥善保管用户快速填写的头像昵称,避免重复弹窗。 插件用户信息功能页 插件申请获取用户头像昵称与用户身份标识符仍保留功能页的形式,不作调整。用户在用户信息功能页中授权之后,插件就可以直接调用 wx.login 和 wx.getUserInfo 。 三、最佳实践调整后,开发者如需获取用户身份标识符只需要调用wx.login接口即可。 开发者若需要在界面中展示用户的头像昵称信息,可以通过<open-data>组件进行渲染,该组件无需用户确认,可以在界面中直接展示。 在部分场景(如社交类小程序)中,开发者需要在获取用户的头像昵称信息,可调用wx.getUserProfile接口,开发者每次通过该接口均需用户确认,请开发者妥善处理调用接口的时机,避免过度弹出弹窗骚扰用户。 微信团队 2021年4月15日
2021-04-15 - 云开发入门
重磅打造的小程序学习路径课,从微信小程序到微信云开发体系化的学习,带来更加顺畅的学习体验。
2021-11-19 - 微信小微商户/特约商户进件V3版本对接
今天我们来讲一下微信小微商户进件V3版本的接口对接。 首先我们来看一下官方的文档: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/applyment/chapter3_1.shtml 根据文档提示,小微商户进件对接协议和特约商户是一样的,只是参数不一样。我们这里以小微商户说明。 首先引入JRE包,后面会用到。这里使用的是maven。 [图片] 上代码 ApplymentBo.java package com.pay.wechat.bo.small.v3; import java.io.ByteArrayInputStream; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; import org.springframework.stereotype.Component; import com.util.OrderIDUtil; import com.pay.wechat.bo.small.v3.util.CertUtil; import com.pay.wechat.bo.small.v3.util.HttpUrlUtil; import com.pay.wechat.bo.small.v3.util.RsaEncryptUtil; import com.pay.contrib.apache.httpclient.util.PemUtil; import net.sf.json.JSONObject; /** * 小微商户进件V3版本<br> * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/applyment/chapter3_1.shtml * * @author libaibai * @version 1.0 2020年9月3日 */ @Component public class ApplymentBo { String business_code = OrderIDUtil.getOrderID(null); public void exe() throws Exception { // 获取微信平台证书 并解析方法在后面 String certString = CertUtil.getCertStr(); ByteArrayInputStream stringStream = new ByteArrayInputStream(certString.getBytes()); // 下面所有加密参数需要的对象 X509Certificate certx = PemUtil.loadCertificate(stringStream); // 超级管理员信息 Map<String, Object> contact_info = new HashMap<String, Object>(); String contact_name = RsaEncryptUtil.rsaEncryptOAEP("张三", certx); // 超级管理员姓名 String contact_id_number = RsaEncryptUtil.rsaEncryptOAEP("000000000000000000", certx); // 超级管理员身份证件号码 String mobile_phone = RsaEncryptUtil.rsaEncryptOAEP("13600000000", certx);// 联系手机 String contact_email = RsaEncryptUtil.rsaEncryptOAEP("zhangsan@sina.com", certx);// 联系邮箱 contact_info.put("contact_name", contact_name); contact_info.put("contact_id_number", contact_id_number); contact_info.put("mobile_phone", mobile_phone); contact_info.put("contact_email", contact_email); // 主体资料 String subject_type = "SUBJECT_TYPE_MICRO"; // 主体类型 String micro_biz_type = "MICRO_TYPE_STORE"; // 小微经营类型 String micro_name = "生态园停车"; // 门店名称 String micro_address_code = "440300"; // 门店省市编码 String micro_address = "南山区沙河西路科技生态园"; // 门店街道名称 String store_entrance_pic = "oO5EoYZsdukezw2NXUxEkb9vTU7PgOu5GyMpNVdMVj5aJAwD85_8kNpakg-s4917roa97XFJf0GPdBNHEvkyf0XPzrOjeKjoBYmEL_eSk7I"; // 门店门口照片 String micro_indoor_copy = "oO5EoYZsdukezw2NXUxEkb9vTU7PgOu5GyMpNVdMVj5aJAwD85_8kNpakg-s4917roa97XFJf0GPdBNHEvkyf0XPzrOjeKjoBYmEL_eSk7I"; // 店内环境照片 // 证件类型,IDENTIFICATION_TYPE_IDCARD String id_doc_type = "IDENTIFICATION_TYPE_IDCARD"; String id_card_copy = "oO5EoYZsdukezw2NXUxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxdafxs"; // 身份证人像面照片 String id_card_national = "oO5EoYZsdukezwdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddHj3QDW_E"; // 身份证国徽面照片 String id_card_name = RsaEncryptUtil.rsaEncryptOAEP("张三", certx); // 身份证姓名 String id_card_number = RsaEncryptUtil.rsaEncryptOAEP("000000000000000000", certx); // 身份证号码 String card_period_begin = "2026-06-06"; // 身份证有效期开始时间示例值:2026-06-06 String card_period_end = "2026-06-06"; // 身份证有效期结束时间示例值:2026-06-06 Map<String, Object> subject_info = new HashMap<String, Object>(); // 主体资料 Map<String, Object> micro_biz_info = new HashMap<String, Object>(); // 小微商户辅助材料 Map<String, Object> micro_store_info = new HashMap<String, Object>(); // 门店场所信息 Map<String, Object> identity_info = new HashMap<String, Object>(); // 经营者身份证件 Map<String, Object> id_card_info = new HashMap<String, Object>(); // 身份证信息 micro_store_info.put("micro_name", micro_name); micro_store_info.put("micro_address_code", micro_address_code); micro_store_info.put("micro_address", micro_address); micro_store_info.put("store_entrance_pic", store_entrance_pic); micro_store_info.put("micro_indoor_copy", micro_indoor_copy); micro_biz_info.put("micro_biz_type", micro_biz_type); micro_biz_info.put("micro_store_info", micro_store_info); id_card_info.put("id_card_copy", id_card_copy); id_card_info.put("id_card_national", id_card_national); id_card_info.put("id_card_name", id_card_name); id_card_info.put("id_card_number", id_card_number); id_card_info.put("card_period_begin", card_period_begin); id_card_info.put("card_period_end", card_period_end); identity_info.put("id_doc_type", id_doc_type); identity_info.put("id_card_info", id_card_info); subject_info.put("subject_type", subject_type); subject_info.put("micro_biz_info", micro_biz_info); subject_info.put("identity_info", identity_info); // 经营资料 String merchant_shortname = "张三停车场"; // 商户简称 String service_phone = "0755222222"; // 客服电话 Map<String, Object> business_info = new HashMap<String, Object>(); business_info.put("merchant_shortname", merchant_shortname); business_info.put("service_phone", service_phone); // 结算规则 // 入驻结算规则ID;请选择结算规则ID,详细参见《费率结算规则对照表》 示例值:小微商户:703 String settlement_id = "703";// String qualification_type = "停车缴费"; // 所属行业;请填写所属行业名称,建议参见《费率结算规则对照表》 示例值:餐饮 Map<String, Object> settlement_info = new HashMap<String, Object>(); settlement_info.put("settlement_id", settlement_id); settlement_info.put("qualification_type", qualification_type); // 收款银行卡 // 账户类型 若主体为小微,可填写:经营者个人银行卡 枚举值: // BANK_ACCOUNT_TYPE_PERSONAL:经营者个人银行卡 // 示例值:BANK_ACCOUNT_TYPE_CORPORATE String bank_account_type = "BANK_ACCOUNT_TYPE_PERSONAL"; String account_name = RsaEncryptUtil.rsaEncryptOAEP("张三", certx); // 开户名称(该字段需进行加密处理) String account_bank = "建设银行"; // 开户银行开户银行,详细参见《开户银行对照表》 示例值:工商银行 String bank_address_code = "440300"; // 开户银行省市编码至少精确到市,详细参见《省市区编号对照表》 示例值:110000 // 1、“开户银行”为17家直连银行无需填写 // 2、“开户银行”为其他银行,则开户银行全称(含支行)和开户银行联行号二选一 // 3、需填写银行全称,如"深圳农村商业银行XXX支行",详细参见《开户银行全称(含支行)对照表》 // 示例值:施秉县农村信用合作联社城关信用社 String bank_name = ""; // 开户银行全称(含支行] String account_number = RsaEncryptUtil.rsaEncryptOAEP("62122xxxxxxxxxx332", certx); // 银行账号(该字段需进行加密处理) Map<String, Object> bank_account_info = new HashMap<String, Object>(); bank_account_info.put("bank_account_type", bank_account_type); bank_account_info.put("account_name", account_name); bank_account_info.put("account_bank", account_bank); bank_account_info.put("bank_address_code", bank_address_code); bank_account_info.put("bank_name", bank_name); bank_account_info.put("account_number", account_number); Map<String, Object> map = new HashMap<String, Object>(); map.put("business_code", business_code); map.put("contact_info", contact_info); map.put("subject_info", subject_info); map.put("business_info", business_info); map.put("settlement_info", settlement_info); map.put("bank_account_info", bank_account_info); try { String body = JSONObject.fromObject(map).toString(); String str = HttpUrlUtil.sendPost(body); System.out.println(str); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { ApplymentBo t = new ApplymentBo(); try { t.exe(); } catch (Exception e) { } } } httpUrlUtil.java package com.pay.wechat.bo.small.v3.util; import java.security.PrivateKey; import java.security.Signature; import java.util.Base64; import javax.ws.rs.core.Response; import org.apache.cxf.jaxrs.client.WebClient; import com.util.Config; import com.util.UUIDUtil; import okhttp3.HttpUrl; /** * HttpUrl工具类 * * @author libaibai * @version 1.0 2020年9月4日 */ public class HttpUrlUtil { public static String SCHEMA = "WECHATPAY2-SHA256-RSA2048"; public static String merchantId = Config.MCHIDSP; // 服务商 public static String POST = "POST"; public static String GET = "GET"; public static String host = "https://api.mch.weixin.qq.com"; public static String APPLY_PATH = "/v3/applyment4sub/applyment/"; // 申请单url public static String CERT_PATH = "/v3/certificates"; // 获取微信平台证书url public static String APPLY_QUERY_PATH = "/v3/applyment4sub/applyment/applyment_id/"; // 查询申请状态 /** * POST请求 */ public static String sendPost(String body) { String url = host + APPLY_PATH; try { // 获取微信平台商户证书序列号 String wxSerialNo = CertUtil.getCertSerialNo(); String authorization = getToken(POST, url, body); WebClient client = WebClient.create(host); client.reset(); client.header("Content-Type", "application/json; charset=UTF-8"); client.header("Accept", "application/json"); client.header("user-agent", "application/json"); client.header("Wechatpay-Serial", wxSerialNo); client.header("Authorization", authorization); client.path(APPLY_PATH); Response r = client.post(body); return r.readEntity(String.class); } catch (Exception e) { return null; } } /** * get请求 */ public static String sendGet() { // 请求URL String url = host + CERT_PATH; try { String authorization = getToken(GET, url, ""); WebClient client = WebClient.create(host); client.reset(); client.header("Content-Type", "application/json; charset=UTF-8"); client.header("Accept", "application/json"); client.header("User-Agent", "application/json"); client.header("Authorization", authorization); client.path(CERT_PATH); Response r = client.get(); return r.readEntity(String.class); } catch (Exception e) { e.printStackTrace(); return null; } } /** * get请求 */ public static String sendGet(String applymentId) { // 请求URL String url = host + APPLY_QUERY_PATH + applymentId; try { String authorization = getToken(GET, url, ""); WebClient client = WebClient.create(host); client.reset(); client.header("Content-Type", "application/json; charset=UTF-8"); client.header("Accept", "application/json"); client.header("User-Agent", "application/json"); client.header("Authorization", authorization); client.path(APPLY_QUERY_PATH + applymentId); Response r = client.get(); return r.readEntity(String.class); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取加密串 * * @param method * @param url * @param body * @return */ public static String getToken(String method, String url, String body) { String nonceStr = UUIDUtil.getUUID32(); long timestamp = System.currentTimeMillis() / 1000; HttpUrl httpUrl = HttpUrl.parse(url); String message = buildMessage(method, httpUrl, timestamp, nonceStr, body); String signature = null; String certificateSerialNo = null; try { signature = sign(message.getBytes("utf-8")); certificateSerialNo = CertUtil.getSerialNo(""); } catch (Exception e) { e.printStackTrace(); } return SCHEMA + " mchid=\"" + merchantId + "\"," + "nonce_str=\"" + nonceStr + "\"," + "timestamp=\"" + timestamp + "\"," + "serial_no=\"" + certificateSerialNo + "\"," + "signature=\"" + signature + "\""; } /** * 得到签名字符串 */ public static String sign(byte[] message) throws Exception { Signature sign = Signature.getInstance("SHA256withRSA"); PrivateKey privateKey = CertUtil.getPrivateKey(); sign.initSign(privateKey); sign.update(message); return Base64.getEncoder().encodeToString(sign.sign()); } public static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) { String canonicalUrl = url.encodedPath(); if (url.encodedQuery() != null) { canonicalUrl += "?" + url.encodedQuery(); } return method + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n"; } } CertUtil.java package com.pay.wechat.bo.small.v3.util; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import org.apache.commons.codec.binary.Base64; import net.sf.json.JSONArray; import net.sf.json.JSONObject; /** * 证书工具类 * * @author libaibai * @version 1.0 2020年9月4日 */ public class CertUtil { // 微信证书私钥路径(从微信商户平台下载,保存在本地) public static String APICLIENT_KEY = "G:\\workspace\\dlysw\\src\\main\\resources\\conf\\cert\\apiclient_key.pem"; // 微信商户证书路径(从微信商户平台下载,保存在本地) public static String APICLIENT_CERT = "G:\\workspace\\dlysw\\src\\main\\resources\\conf\\cert\\apiclient_cert.pem"; /** * 获取私钥。 * * @param apiclient_key 私钥文件路径 (required) * @return 私钥对象 */ public static PrivateKey getPrivateKey() throws IOException { String content = new String(Files.readAllBytes(Paths.get(APICLIENT_KEY)), StandardCharsets.UTF_8); try { String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", ""); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey))); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("当前Java环境不支持RSA", e); } catch (InvalidKeySpecException e) { throw new RuntimeException("无效的密钥格式"); } } /** * 获取商户证书。 * * @param filename 证书文件路径 (required) * @return X509证书 */ public static X509Certificate getCertificate(String filename) throws IOException { InputStream fis = new FileInputStream(APICLIENT_CERT); try (BufferedInputStream bis = new BufferedInputStream(fis)) { CertificateFactory cf = CertificateFactory.getInstance("X509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(bis); cert.checkValidity(); return cert; } catch (CertificateExpiredException e) { throw new RuntimeException("证书已过期", e); } catch (CertificateNotYetValidException e) { throw new RuntimeException("证书尚未生效", e); } catch (CertificateException e) { throw new RuntimeException("无效的证书文件", e); } } /** * 获取商户证书序列号 * * @param certPath 获取商户证书序列号 传递商号证书路径 apiclient_cert * @return * @throws IOException */ public static String getSerialNo(String certPath) throws IOException { X509Certificate certificate = getCertificate(certPath); return certificate.getSerialNumber().toString(16).toUpperCase(); } /** * 获取微信平台证书序列号 * * @return * @throws Exception */ public static String getCertSerialNo() throws Exception { try { String str = HttpUrlUtil.sendGet(); System.out.println(str); JSONObject json = JSONObject.fromObject(str); JSONArray jsonArray = JSONArray.fromObject(json.optString("data")); JSONObject jsonObject = jsonArray.getJSONObject(0); return jsonObject.optString("serial_no"); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 获取微信平台证书 * * @return * @throws Exception */ public static String getCertStr() throws Exception { try { String str = HttpUrlUtil.sendGet(); JSONObject json = JSONObject.fromObject(str); JSONArray jsonArray = JSONArray.fromObject(json.optString("data")); JSONObject jsonObject = jsonArray.getJSONObject(0); JSONObject jsonCert = JSONObject.fromObject(jsonObject.optString("encrypt_certificate")); String certKeyString = AesUtil.decryptToString(jsonCert.getString("associated_data").getBytes(), jsonCert.getString("nonce").getBytes(), jsonCert.getString("ciphertext")); return certKeyString; } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) { try { System.out.println(CertUtil.getCertStr()); } catch (Exception e) { e.printStackTrace(); } } } AesUtil.java package com.dlysw.pay.wechat.bo.small.v3.util; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Base64; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import com.dlyspublic.util.Config; /** * * @author libaibai * @version 1.0 2020年9月8日 */ public class AesUtil { public static final int TAG_LENGTH_BIT = 128; public static byte[] aesKey = Config.AES_KEY_APIV3.getBytes(); // APIv3密钥 public static String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws Exception { try { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); SecretKeySpec key = new SecretKeySpec(aesKey, "AES"); GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce); cipher.init(Cipher.DECRYPT_MODE, key, spec); cipher.updateAAD(associatedData); return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8"); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new IllegalStateException(e); } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { throw new IllegalArgumentException(e); } } } ok,直接运行ApplymentBo.java里面的main方法,得到返回结果 {"applyment_id": xxxxxxxxxxxxxx} 就表示进件成功了,我们登陆到商户平台看看。创建员工=API的就是刚才我们申请成功的。 [图片] 最后,我们也把状态查询的代码也贴一下 ApplymentQueryBo.java package com.pay.wechat.bo.small.v3; import org.springframework.stereotype.Component; import com.pay.wechat.bo.small.v3.util.HttpUrlUtil; /** * 小微商户进件查询 * * @author libaibai * @version 1.0 2020年9月8日 */ @Component public class ApplymentQueryBo { /** * 执行 * * @param applymentId 申请单号 * @return */ public String query(String applymentId) { String str = HttpUrlUtil.sendGet(applymentId); System.out.println(str); return str; } public static void main(String[] args) { String applymentId = "200000xxxxxxxxxx"; ApplymentQueryBo b = new ApplymentQueryBo(); b.query(applymentId); } } 返回成功: {"applyment_id":200000xxxxxxxxxx,"applyment_state":"APPLYMENT_STATE_TO_BE_SIGNED","applyment_state_msg":"请超级管理员使用微信打开返回的“签约链接”,根据页面指引完成签约","audit_detail":[],"business_code":"WEB|1590030703","sign_url":"https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQGb7zwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyeWZaMDl3b3JlUjIxbG9PLU52Y1YAAgRY5VZfAwQAjScA","sub_mchid":"15947111111"}
2020-09-11 - 【新手必看】微信支付V2之傻瓜式1秒接入
微信支付之前也对接过几次,因为时隔久远,每次对接都要挠头一番,可怜了发际线了,我的宝你在哪里,你再不来我头发掉光了😭 这次决定记录下来,以后对接方便新人也方便自己,说真,其实我是看上官方的拉杆箱了🥰 欲练神功,必先挠头,快快乐乐吃西瓜,清清凉凉一夏天👻 如果你是第一次对接支付: 1.你得申请一个服务号,并通过微信认证 2.获取参数看图:往下看 ⬇️ 3.注册微信商户平台并配置API密钥 [图片] [图片] 第一式:随便起个文件名,最好要有特色 //命名为:wwxpay-mini.php 扔到你的微信支付授权目录,就这一个文件已经实现了jsapi支付,无需下载其他sdk 第二式:调试工具 微信公众平台支付接口调试工具:https://pay.weixin.qq.com/wiki/tools/signverify/ (调试错误的时候可以用得上,新手暂时略过) 第三式:上代码 操作方法:将此文件备注位置-【商户参数】/【订单信息】两处的参数,改为你自己的即可,其中订单参数可以为动态获取,如果你只有一个商品那就写死吧 <?php /*下单参数: 公众账号ID 字段: appid 商户号 字段: mch_id 随机字符串 字段: nonce_str 签名 字段: sign 签名类型 字段: sign_type "MD5" 商品描述 字段: body 商户订单号 字段: out_trade_no 标价金额 字段: total_fee 终端IP 字段: spbill_create_ip 通知地址 字段: notify_url 交易类型 字段: trade_type "JSAPI" 转化为以下格式: <xml> <appid>wx2421b1c4370ec43b</appid> <attach>支付测试</attach> <body>JSAPI支付测试</body> <mch_id>10000100</mch_id> <detail><![CDATA[{ "goods_detail":[ { "goods_id":"iphone6s_16G", "wxpay_goods_id":"1001", "goods_name":"iPhone6s 16G", "quantity":1, "price":528800, "goods_category":"123456", "body":"苹果手机" }, { "goods_id":"iphone6s_32G", "wxpay_goods_id":"1002", "goods_name":"iPhone6s 32G", "quantity":1, "price":608800, "goods_category":"123789", "body":"苹果手机" } ] }]]></detail> <nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str> <notify_url>https://wxpay.wxutil.com/pub_v2/pay/notify.v2.php</notify_url> <openid>oUpF8uMuAJO_M2pxb1Q9zNjWeS6o</openid> <out_trade_no>1415659990</out_trade_no> <spbill_create_ip>14.23.150.211</spbill_create_ip> <total_fee>1</total_fee> <trade_type>JSAPI</trade_type> <sign>0CB01533B8C1EF103065174F50BCA001</sign> </xml> 返回值: 返回状态码 字段: return_code 返回值: SUCCESS/FAIL 返回信息 字段: return_msg 返回值: OK 成功的话,在return_code为SUCCESS的时候有返回: 公众账号ID 字段: appid 商户号 字段: mch_id 签名 字段: sign 业务结果 字段: result_code 错误代码 字段: err_code 返回值: SUCCESS/FAIL(错误的时候返回) 错误码解析: INVALID_REQUEST 参数错误 参数格式有误或者未按规则上传 订单重入时,要求参数值与原请求一致,请确认参数问题 NOAUTH 商户无此接口权限 商户未开通此接口权限 请商户前往申请此接口权限 NOTENOUGH 余额不足 用户账号余额不足 用户账号余额不足,请用户充值或更换支付卡后再支付 ORDERPAID 商户订单已支付 商户订单已支付,无需重复操作 商户订单已支付,无需更多操作 ORDERCLOSED 订单已关闭 当前订单已关闭,无法支付 当前订单已关闭,请重新下单 SYSTEMERROR 系统错误 系统超时 系统异常,请用相同参数重新调用 APPID_NOT_EXIST APPID不存在 参数中缺少APPID 请检查APPID是否正确 MCHID_NOT_EXIST MCHID不存在 参数中缺少MCHID 请检查MCHID是否正确 APPID_MCHID_NOT_MATCH appid和mch_id不匹配 appid和mch_id不匹配 请确认appid和mch_id是否匹配 LACK_PARAMS 缺少参数 缺少必要的请求参数 请检查参数是否齐全 OUT_TRADE_NO_USED 商户订单号重复 同一笔交易不能多次提交 请核实商户订单号是否重复提交 SIGNERROR 签名错误 参数签名结果不正确 请检查签名参数和方法是否都符合签名算法要求 XML_FORMAT_ERROR XML格式错误 XML格式错误 请检查XML参数格式是否正确 REQUIRE_POST_METHOD 请使用post方法 未使用post传递参数 请检查请求参数是否通过post方法提交 POST_DATA_EMPTY post数据为空 post数据不能为空 请检查post数据是否为空 NOT_UTF8 编码格式错误 未使用指定编码格式 请使用UTF-8编码格式 接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder */ header('Content-type:text/html; Charset=utf-8');//设置网页编码 ini_set('date.timezone', 'Asia/Shanghai'); //商户参数 $appKey = 'xxxxxxxxxxxxxxxxxx';//公众号的APPKey $config = array( 'mch_id' => 'xxxxxxxxxx',//微信支付平台商户号 'appid' => 'xxxxxxxxxxxxxxx',//公众号平台的APPID 'key' => 'xxxxxxxxxxxxxxxxxxxxxxx' //https://pay.weixin.qq.com 帐户设置-安全设置-API安全-API密钥V2-设置API密钥 ); //订单信息 $unified = array( 'appid' => $config['appid'],//公众账号ID 'attach' => '饭团',//附加数据 'body' => "饭团子",//商品描述 'mch_id' => $config['mch_id'],//商户号 'nonce_str' => NoStr(),//随机字符串 'notify_url' => "http://pay.xxxxxx.com/wxpay/api/returns.php",//通知地址 'openid' => GetOpenid($config), 'out_trade_no' => strval(date("YmdH").time()),//商户订单号 'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],//终端IP 'total_fee' => intval(0.01*100), //微信会自己减去两位数*100补回,标价金额 'trade_type' => 'JSAPI',//交易类型 ); $unified['sign'] = getSign($unified, $config['key']);//获取签名 // echo "<pre>";print_r($unified);echo "</pre>";die; // die(arrayToXml($unified)); $responseXml = curlPost('https://api.mch.weixin.qq.com/pay/unifiedorder', arrayToXml($unified));//转换为xml并发送 $unifiedOrder = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);//解析XML为对象 if ($unifiedOrder === false) { die('parse xml error');//解析错误 } if ($unifiedOrder->return_code != 'SUCCESS') { die($unifiedOrder->return_msg);//返回成功结果 } if ($unifiedOrder->result_code != 'SUCCESS') { die($unifiedOrder->err_code->des);//返回失败结果 } $arr = array( "appId" => $config['appid'],//公众账号ID "timeStamp" => strval(time()),//这里是字符串的时间戳 "nonceStr" => NoStr(),//随机字符串 "package" => "prepay_id=" . $unifiedOrder->prepay_id,//订单详情扩展字符串 "signType" => 'MD5',//声明加密类型 ); $arr['paySign'] = getSign($arr, $config['key']);//获取调起支付的签名 $jsApiParameters=json_encode($arr); ?> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>FT-收银台</title> <script type="text/javascript"> /* JSAPI调起支付 公众号id appId 时间戳 timeStamp 随机字符串 nonceStr String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,32位。推荐随机数生成算法 订单详情扩展字符串 package String(128) prepay_id=123456789 统一下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** 签名方式 signType MD5 签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致 签名 paySign 是 String(64) C380BEC2BFD727A4B6845133519F3AD6 签名,详见签名生成算法:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3 */ function jsApiCall() { WeixinJSBridge.invoke( 'getBrandWCPayRequest', <?php echo $jsApiParameters; ?>, function(res){ WeixinJSBridge.log(res.err_msg); alert(res.err_code+res.err_desc+res.err_msg); } ); } function callpay() { if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', jsApiCall, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', jsApiCall); document.attachEvent('onWeixinJSBridgeReady', jsApiCall); } }else{ jsApiCall(); } } </script> </head> <body> <br/> <font color="#9ACD32"><b>该笔订单支付金额为<span style="color:#f00;font-size:50px">0.01元</span>钱</b></font><br/><br/> <div align="center"> <button style="width:210px; height:50px; border-radius: 15px;background-color:#FE6714; border:0px #FE6714 solid; cursor: pointer; color:white; font-size:16px;" type="button" onclick="callpay()" >立即支付</button> </div> </body> </html> <?php //所有的函数都在这里了 function GetOpenid(array $config){ //通过code获得openid if (!isset($_GET['code'])){ //触发微信返回code码 $scheme = $_SERVER['HTTPS']=='on' ? 'https://' : 'http://'; $baseUrl = urlencode($scheme.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].$_SERVER['QUERY_STRING']); $url = __CreateOauthUrlForCode($baseUrl,$config); Header("Location: $url"); exit(); } else { //获取code码,以获取openid $code = $_GET['code']; $openid = GetOpenidFromMp($code,$config); return $openid; } } function GetOpenidFromMp($code,array $config){ $url = __CreateOauthUrlForOpenid($code,$config); $res = curlGet($url); //取出openid $data = json_decode($res,true); // $this->data = $data; $openid = $data['openid']; return $openid; } function __CreateOauthUrlForOpenid($code,array $config){ $urlObj["appid"] = $config['appid']; $urlObj["secret"] = $GLOBALS['appKey']; $urlObj["code"] = $code; $urlObj["grant_type"] = "authorization_code"; $bizString = toparams2($urlObj); return "https://api.weixin.qq.com/sns/oauth2/access_token?".$bizString; } /** * 构造获取code的url连接 * @param string $redirectUrl 微信服务器回跳的url,需要url编码 * @return 返回构造好的url */ function __CreateOauthUrlForCode($redirectUrl,array $config){ $urlObj["appid"] = $config['appid']; $urlObj["redirect_uri"] = "$redirectUrl"; $urlObj["response_type"] = "code"; $urlObj["scope"] = "snsapi_base"; $urlObj["state"] = "STATE"."#wechat_redirect"; $bizString = toparams2($urlObj); return "https://open.weixin.qq.com/connect/oauth2/authorize?".$bizString; } function getSign($params, $key){ //生成签名 ksort($params, SORT_STRING);//把值作为字符串来排序 $unSignParaString = toparams1($params, false); $signStr = strtoupper(md5($unSignParaString . "&key=" . $key));//拼接字符并转换为大写 return $signStr; } function arrayToXml($arr) { $xml = "<xml>"; foreach ($arr as $key => $val) { if (is_numeric($val)) {//检测变量是否为数字或数字字符串 $xml .= "<" . $key . ">" . $val . "</" . $key . ">"; } else $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">"; } $xml .= "</xml>"; return $xml; } function toparams1($paraMap, $urlEncode = false){ //签名数组排序并拼接 $buff = ""; ksort($paraMap);//对数组进行排序:ABCDEFG…… foreach ($paraMap as $k => $v) { if (null != $v && "null" != $v) { if ($urlEncode) { $v = urlencode($v); } $buff .= $k . "=" . $v . "&"; } } $reqPar = false; if (strlen($buff) > 0) { $reqPar = substr($buff, 0, strlen($buff) - 1);//移除多余的& } return $reqPar; } function toparams2($urlObj){ //把数组转成URL参数:appid=123456&sign=xxxxxxxxxxxxxxxxx $buff = ""; foreach ($urlObj as $k => $v) { if($k != "sign") $buff .= $k . "=" . $v . "&"; } $buff = trim($buff, "&");//移除多余的& return $buff; } function curlGet($url = '', $options = array()){ //get访问 $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_TIMEOUT, 30); if (!empty($options)) { curl_setopt_array($ch, $options); } //https请求 不验证证书和host curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $data = curl_exec($ch); curl_close($ch); return $data; } function curlPost($url = '', $postData = '', $options = array()){ //post访问 if (is_array($postData)) { $postData = http_build_query($postData);//把数组转成URL参数:appid=123456&sign=xxxxxxxxxxxxxxxxx } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL超时为30秒 if (!empty($options)) { curl_setopt_array($ch, $options); } //https请求 不验证证书和host curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $data = curl_exec($ch); curl_close($ch); return $data; } function NoStr($length = 16){ //随机字符串默认16位 $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $str = ''; for ($i = 0; $i < $length; $i++) { $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); } return $str; } ?> 我喜欢面向过程emm,别笑话我🤣 别问为啥这么多备注,因为怕你笨🤣 总结: 1. 申请服务号,注册微信商户平台,获取:Appid Key Api密钥 商户号 2. 创建wxpay-mini.php【名称随意,但是一定放到授权目录】 3.【商户参数】/【订单信息】两处的参数,改为你自己的参数 4.运行支付测试结果 这你还不会的话,来找我,看我怎么揍你🤪 我还没对象,拉杆箱可以不要,官方给我介绍个对象呗,别说给我New一个😭 还没有用过微信云托管的朋友可以看我另一篇教程:https://developers.weixin.qq.com/community/minihome/article/doc/000068648fcae81db5fd0f17e58413 暂时就写这么多,再熬天就要亮了😇
2022-05-27 - 【新手必看】微信云托管PHP+Nginx配置使用教程
注册登录就不用讲了,自行领悟 官方提供的一键部署并不怎么实用,我把demo部署上去有什么用,又不能修改,干瞪眼🤣 因为没用过Docker之后查了又查,并没有关于PHP的部署,全是Python Nodejs Java的教程,是PHP不香了吗🥵 第一步创建实例,我们需要先了解下管官方要的Dockerfile文件的写法: Dockerfile ,也别了解了直接复制就行😂 # 不建议更换alpine3.13之后会有问题,什么问题我也不知道,不要动就对了 FROM alpine:3.13 # 容器默认时区为UTC,如需使用上海时间请启用以下时区设置命令 #RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone # 设置国内镜像源并安装PHP+Nginx+Zip RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tencent.com/g' /etc/apk/repositories \ && apk add --update --no-cache \ php7 \ php7-json \ php7-ctype \ php7-exif \ php7-pdo \ php7-pdo_mysql \ php7-fpm \ php7-curl \ nginx \ zip \ && rm -f /var/cache/apk/* # 设置网站目录,这个名字你喜欢就好 WORKDIR /app # 将当前目录下所有文件拷贝到/app (.dockerignore中文件除外) COPY . /app # 替换nginx、fpm、php配置并赋予777权限,不赋予权限修改或写日志有问题,配置文件放到本地根目录conf目录下 RUN cp /app/conf/nginx.conf /etc/nginx/conf.d/default.conf \ && cp /app/conf/fpm.conf /etc/php7/php-fpm.d/www.conf \ && cp /app/conf/php.ini /etc/php7/php.ini \ && mkdir -p /run/nginx \ && chmod -R 777 /app \ && chmod -R 777 /etc \ && mv /usr/sbin/php-fpm7 /usr/sbin/php-fpm # 暴露端口 # 此处端口必须与「服务设置」-「流水线」以及「手动上传代码包」部署时填写的端口一致,否则会部署失败。 EXPOSE 80 # 执行启动命令. # 写多行独立的CMD命令是错误写法!只有最后一行CMD命令会被执行,之前的都会被忽略,导致业务报错。 # 请参考[Docker官方文档之CMD命令](https://docs.docker.com/engine/reference/builder/#cmd) CMD ["sh", "run.sh"] run.sh 直接复制,不要改,不要改,不要改 #!/bin/sh # 后台启动 php-fpm -D # 关闭后台启动,hold住进程 nginx -g 'daemon off;' 有了这俩万能配置文件,你只需要: 放到你的项目 ➡️ 根目录 ➡️ 打包Zip压缩包 ➡️ 微信云托管登录 ➡️ 创建实例 ➡️ 手动上传代码 ➡️ 选定压缩包 ➡️ 提交部署 ➡️ 等待项目启动 如果想要在线编辑,创建目录:manager,然后下载它:https://github.com/657258535/Great-Linux-Manager ,把它扔到manager目录就可以在线编辑了,不过只能用于测试,正式版请通过发布部署完成 如果实例会自动停止,解决办法看图: [图片] 其他的问题暂时没遇到,至此记录结束🇨🇳
2022-05-24 - 【特约商进件】请按最新的接口文档要求进件,怎么解?
之前做了一个特约商进件的功能,原来是没问题的; 现在突然,告诉我,有问题。 {"code":"PARAM_ERROR","message":"请按最新的接口文档要求进件"} 这个怎么解,有遇到这个问题的朋友么,给点思路! 特约商户进件,提交申请单 请求URL:https://api.mch.weixin.qq.com/v3/applyment4sub/applyment/ End === update at 2022.07.08 === 文档更新日志 , 可以看这个更新日志,了解最新的改动! 1、超级管理员,contact_info 增加 contact_type 字段,【必填】 2、若主体类型是个体户(非企业),不要传递 subject_info/identity_info/owner 字段 3、最终受益人现在改为 Array , ubo_info_list [图片] End
2022-07-08 - 微信支付 privateKey是什么
[图片]这个privateKey用证书里面的 apiclient_key.pem 异常:应答的微信支付签名验证失败; 使用CertificateDownloader-1.1.jar工具参考https://developers.weixin.qq.com/community/develop/doc/000e4a0d5dc1486acc19c6fd15bc00?_at=1569021781371生成的pem异常:无效的密钥格式; 请问这个privateKey是官方已经提供了还是需要自己生成?生成需要怎么样生成才能用……
2021-02-04 - 付款码查询openid没有返回具体错误原因?
付款码支付中api授权码查询openid接口返回失败,但是没有具体失败原因查询接口https://api.mch.weixin.qq.com/tools/authcodetoopenid 参数: <appid>wx*******df</appid> <mch_id>1******2</mch_id> <sub_mch_id>1********8</sub_mch_id> <nonce_str>13604948783521872768488079841153</nonce_str> <sign>42EB36614A8FF93FFF23C311D2472E742A7ABAF943AAF32A236398EFE9C6B15D</sign> <auth_code>134570572084180697</auth_code> 返回: <xml><return_code><![CDATA[FAIL]]></return_code>
2020-12-17 - 小程序发布成功后搜索不到怎么处理?
小程序于1.17号发布成功,在首页搜索小程序是可以搜索到的(开单乐享),在发现-小程序-搜索,搜索不到,请问这是什么情况,需要怎么解决?
2020-01-18 - 小程序搜索优化指南(SEO)
2019年上半年微信发布了基于小程序页面的搜索,为了让我们更好地发现及理解小程序的页面,结合过去一段时间来我们遇到的各种情况,我们强烈建议各位开发者花一些宝贵的时间认真阅读本文:) 爬虫访问小程序内页面时,会携带特定的 user-agent "mpcrawler" 及场景值:1129 1. 小程序里跳转的页面 (url) 可被直接打开。 小程序页面内的跳转url是我们爬虫发现页面的重要来源,且搜索引擎召回的结果页面 (url) 是必须能直接打开,不依赖上下文状态的。特别的:建议页面所需的参数都包含在url 2. 页面跳转优先采用navigator组件。 小程序提供了两种页面路由方式: a.navigator 组件 b. 路由 API,包括 navigateTo / redirectTo / switchTab / navigateBack / reLaunch 建议使用 navigator 组件,若不得不使用API,可在爬虫访问时屏蔽针对点击设置的时间锁或变量锁。 3.清晰简洁的页面参数。 结构清晰、简洁、参数有含义的 querystring 对抓取以及后续的分析都有很大帮助,但是将 JSON 数据作为参数的方式是比较糟糕的实现。 4. 必要的时候才请求用户进行授权、登录、绑定手机号等。 建议在必须的时候才要求用户授权(比如阅读文章可以匿名,而发表评论需要留名)。 5. 我们不收录 web-view 中的任何内容。 我们暂时做不到这一点,长期来看,我们可能也做不到。 6. 利用 sitemap 配置引导爬虫抓取,同时屏蔽无搜索价值的路径。 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html 7. 设置一个清晰的标题和页面缩略图。 页面标题和缩略图对于我们理解页面和提高曝光转化有重要的作用。 通过wx.setNavigationBarTitle或 自定义转发内容onShareAppMessage对页面的标题和缩略图设置,另外也为 video、audio 组件补齐 poster /poster-for-crawler属性。 8. 使用页面路径推送能力 可极大丰富微信可以收录的内容,进而提高小程序内容的曝光机会。请参考: https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/search/search.submitPages.html
2020-01-14 - 微信支付分 支持服务商模式吗?我看文档都是直连商户啊。
看了微信支付分的相关API,标注的API都是面向直连商户的,请问下微信支付分支持服务商模式吗?如果支持需要怎样开通呢?
2020-10-28 - 微信支付服务商模式下,希望能为名下的每个“特约子商户”单独设置“退款结果回调通知URL"
我们公司申请的是微信支付服务商的账号类型,旨在为我们的企业客户提供在线支付能力的接入。 但是我们在实际运营中发现,偶尔会有必须从 “服务商管理后台” 来为子商户进行退款操作的情况(绝大部分情况下会使用API退款模式,某些特殊场景下只能使用服务商平台人工进行退款)。可是目前的平台人工退款,只能配置统一的 "退款结果回调通知URL"(如图1),所有子商户都是共用一个退款回调地址,无法分别设置。 [图片] 图1 但是在实际情况中,我们名下不同的子商户,他们的服务器接口地址都是不同的,他们并没有部署在同一个域名网址下。所以这时发起手动退款,永远都只能通知到这一个固定的网址上,实际上子商户的服务器,根本就无法接收到退款成功通知。 所以希望能增加一个功能,在子商户的开发设置界面,能为每个子商户单独设置他们的退款回调地址(如图2) [图片] 希望这个建议能够被采纳,感谢!!
2020-12-16 - wx66e0aab98b4d306a的appid,获取手机号提示:"该appid没有权限"?
button class="wxlogin-btn loginButton" open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber" >微信用户快捷登陆
2020-08-10 - 网络不好的点击右上角扫一扫,网络正常时会服务通知会还原刚刚的扫一扫结果,应用上怎么区分?
[图片] 重现步骤: 1.切断网络,按微信右上角的扫一扫, 2. 服务通知出现扫码结果通知 疑问: 我们应用是需要区分正常扫码进入 或者是在服务通知上点击扫码结果连接进入这两种情景? 经过测试,wx.getLaunchOptionsSync中获取的场景值和其他都是一摸一样的,可否加个标识字段区分正常扫码和服务通知点击这两种情况? 不然会用户会出现好奇而点击,我们应用又区分不了,无法提示用户,造成用户误操作(人离开现场,机器扫码开门,附近的人洗劫一空),钱财两空的情况 谢谢
2019-12-09 - 微信断网扫一扫,恢复网络连接收到扫一扫通知
[图片]前段时间并没有这个通知,即使断网扫码!这个是一个扫码结账的后台链接。我现在直接点进去该通知就可以直接付钱。
2019-09-10 - v3图片上传接口java代码(HttpURLConnection实现)
折腾了一天,终于把这个图片接口(https://api.mch.weixin.qq.com/v3/merchant/media/upload)调通了。看到社区里面有不少小伙伴遇到了一样的问题,这里简单分享一下我的测试代码。因为我是用java开发的,所以下面内容的代码示例都是java的。 水平有限,如有错漏之处,请多多指教。 [代码]public[代码] [代码]class[代码] [代码]uploadFileTest {[代码] [代码] [代码][代码]public[代码] [代码]static[代码] [代码]void[代码] [代码]main(String[] args) {[代码][代码] [代码][代码]// TODO Auto-generated method stub[代码][代码] [代码] [代码] [代码][代码]try[代码] [代码]{[代码][代码] [代码] [代码] [代码][代码]// 换行符[代码][代码] [代码][代码]String LINE_END = [代码][代码]"\r\n"[代码][代码];[代码][代码] [代码][代码]String PREFIX = [代码][代码]"--"[代码][代码];[代码][代码] [代码][代码]// 定义数据分隔线[代码][代码] [代码][代码]String BOUNDARY = [代码][代码]""[代码][代码];[代码][代码] [代码] [代码] [代码][代码]//商户号[代码][代码] [代码][代码]String mchid = [代码][代码]""[代码][代码];[代码][代码] [代码][代码]//证书序列号[代码][代码] [代码][代码]String serial_no = [代码][代码]""[代码][代码];[代码][代码] [代码][代码]//商户私钥([代码]拷贝apiclient_key.pem文件里-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----之间的内容)[代码] [代码][代码]String rsaPrivateKeyFile = [代码][代码]""[代码][代码];[代码][代码] [代码][代码]//微信支付平台公钥文件[代码][代码] [代码][代码]String rsaPublicKey = [代码][代码]""[代码][代码];[代码][代码] [代码] [代码] [代码][代码]//时间戳[代码][代码] [代码][代码]String timestamp = Long.toString(System.currentTimeMillis()/[代码][代码]1000[代码][代码]);[代码][代码] [代码][代码]//随机数[代码][代码] [代码][代码]String nonce_str = [代码][代码]""[代码][代码];[代码][代码] [代码] [代码] [代码][代码]//图片文件[代码][代码] [代码][代码]String filePath = [代码][代码]""[代码][代码];[代码][代码]//文件路径[代码][代码] [代码][代码]File file = [代码][代码]new[代码] [代码]File(filePath);[代码][代码] [代码][代码]String filename = file.getName();[代码][代码]//文件名[代码][代码] [代码][代码]String fileSha256 = DigestUtils.sha256Hex([代码]new[代码] [代码]FileInputStream(file)[代码]);[代码][代码]//文件sha256值[代码][代码] [代码] [代码] [代码][代码]//拼签名串[代码][代码] [代码][代码]StringBuffer sb = [代码][代码]new[代码] [代码]StringBuffer();[代码][代码] [代码][代码]sb.append([代码][代码]"POST"[代码][代码]).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]sb.append([代码][代码]"/v3/merchant/media/upload"[代码][代码]).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]sb.append(timestamp).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]sb.append(nonce_str).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]sb.append([代码][代码]"{\"filename\":\""[代码][代码]+filename+[代码][代码]"\",\"sha256\":\""[代码][代码]+fileSha256+[代码][代码]"\"}"[代码][代码]).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]System.out.println([代码][代码]"签名原串:"[代码][代码]+sb.toString());[代码][代码] [代码] [代码] [代码][代码]//计算签名[代码][代码] [代码][代码]String sign = [代码][代码]new[代码] [代码]String(Base64.encodeBase64(signRSA(sb.toString(),rsaPrivateKey)));[代码][代码] [代码][代码]System.out.println([代码][代码]"签名sign值:"[代码][代码]+sign);[代码][代码] [代码] [代码] [代码][代码]//拼装http头的Authorization内容[代码][代码] [代码][代码]String authorization =[代码][代码]"WECHATPAY2-SHA256-RSA2048 mchid=\""[代码][代码]+mchid+[代码][代码]"\",nonce_str=\""[代码][代码]+nonce_str+[代码][代码]"\",signature=\""[代码][代码]+sign+[代码][代码]"\",timestamp=\""[代码][代码]+timestamp+[代码][代码]"\",serial_no=\""[代码][代码]+serial_no+[代码][代码]"\""[代码][代码];[代码][代码] [代码][代码]System.out.println([代码][代码]"authorization值:"[代码][代码]+authorization);[代码][代码] [代码] [代码] [代码][代码]//接口URL[代码][代码] [代码][代码]URL url = [代码][代码]new[代码] [代码]URL([代码][代码]"https://api.mch.weixin.qq.com/v3/merchant/media/upload"[代码][代码]);[代码][代码] [代码][代码]HttpURLConnection conn = (HttpURLConnection) url.openConnection();[代码][代码] [代码][代码]// 设置为POST[代码][代码] [代码][代码]conn.setRequestMethod([代码][代码]"POST"[代码][代码]);[代码][代码] [代码][代码]// 发送POST请求必须设置如下两行[代码][代码] [代码][代码]conn.setDoOutput([代码][代码]true[代码][代码]);[代码][代码] [代码][代码]conn.setDoInput([代码][代码]true[代码][代码]);[代码][代码] [代码][代码]conn.setUseCaches([代码][代码]false[代码][代码]);[代码][代码] [代码][代码]// 设置请求头参数[代码][代码] [代码][代码]conn.setRequestProperty([代码][代码]"Charsert"[代码][代码], [代码][代码]"UTF-8"[代码][代码]);[代码][代码] [代码][代码]conn.setRequestProperty([代码][代码]"Accept"[代码][代码],[代码][代码]"application/json"[代码][代码]);[代码][代码] [代码][代码]conn.setRequestProperty([代码][代码]"Content-Type"[代码][代码], [代码][代码]"multipart/form-data; boundary="[代码] [代码]+ BOUNDARY);[代码][代码] [代码][代码]conn.setRequestProperty([代码][代码]"Authorization"[代码][代码], authorization);[代码][代码] [代码] [代码] [代码][代码]DataOutputStream dos = [代码][代码]new[代码] [代码]DataOutputStream(conn.getOutputStream());[代码][代码] [代码] [代码] [代码][代码]//拼装请求内容第一部分[代码][代码] [代码][代码]StringBuilder strSb = [代码][代码]new[代码] [代码]StringBuilder();[代码][代码] [代码][代码]strSb.append(PREFIX).append(BOUNDARY).append(LINE_END)[代码][代码] [代码][代码].append([代码][代码]"Content-Disposition: form-data; name=\"meta\";"[代码] [代码]+ LINE_END)[代码][代码] [代码][代码].append([代码][代码]"Content-Type: application/json; "[代码] [代码]+ LINE_END)[代码][代码] [代码][代码].append(LINE_END)[代码][代码]// 空行[代码][代码] [代码][代码].append([代码][代码]"{\"filename\":\""[代码][代码]+filename+[代码][代码]"\",\"sha256\":\""[代码][代码]+fileSha256+[代码][代码]"\"}"[代码][代码])[代码][代码] [代码][代码].append(LINE_END);[代码] dos.write(strSb.toString().getBytes()); [代码][代码][代码] [代码][代码]dos.flush();[代码][代码] [代码] [代码] [代码][代码]//拼装请求内容第二部分[代码][代码] [代码][代码]StringBuilder fileSbStart = [代码][代码]new[代码] [代码]StringBuilder();[代码][代码] [代码][代码]fileSbStart.append(PREFIX).append(BOUNDARY).append(LINE_END)[代码][代码] [代码][代码].append([代码][代码]"Content-Disposition: form-data; name=\"file\"; filename=\""[代码][代码]+ filename+ [代码][代码]"\";"[代码] [代码]+ LINE_END)[代码][代码] [代码][代码].append([代码][代码]"Content-Type: image/jpeg"[代码] [代码]+ LINE_END) [代码][代码] [代码][代码].append(LINE_END);[代码][代码]// 空行[代码] dos.write(fileSbStart.toString().getBytes()); [代码][代码][代码] [代码][代码]dos.flush();[代码][代码] [代码] [代码] [代码][代码]//文件二进制内容[代码][代码] [代码][代码]InputStream is = [代码][代码]new[代码] [代码]FileInputStream(file);[代码][代码] [代码][代码]byte[代码][代码][] buffer = [代码][代码]new[代码] [代码]byte[代码][代码][[代码][代码]1024[代码][代码]];[代码][代码] [代码][代码]int[代码] [代码]len = [代码][代码]0[代码][代码];[代码][代码] [代码][代码]while[代码] [代码]((len = is.read(buffer)) != -[代码][代码]1[代码][代码]){[代码][代码] [代码][代码]dos.write(buffer,[代码][代码]0[代码][代码],len);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]is.close();[代码][代码] [代码] [代码] [代码][代码]//拼装请求内容结尾[代码][代码] [代码][代码]StringBuilder fileSbEnd = [代码][代码]new[代码] [代码]StringBuilder();[代码][代码] [代码][代码]fileSbEnd.append(LINE_END)[代码][代码] [代码][代码].append(PREFIX).append(BOUNDARY).append(PREFIX)[代码][代码] [代码][代码].append(LINE_END);[代码][代码] [代码] dos.write(fileSbEnd.toString().getBytes()); [代码][代码][代码] [代码][代码]dos.flush();[代码][代码] [代码][代码]dos.close();[代码][代码] [代码] [代码] [代码][代码]//接收返回[代码][代码] [代码][代码]//打印返回头信息[代码][代码] [代码][代码]System.out.println([代码][代码]"接口返回头信息:"[代码][代码]);[代码][代码] [代码][代码]Map<String, List<String>> responseHeader = conn.getHeaderFields();[代码][代码] [代码][代码]for[代码] [代码](Map.Entry<String, List<String>> entry : responseHeader.entrySet()) {[代码][代码] [代码][代码]System.out.println(entry.getKey()+[代码][代码]":"[代码] [代码]+ entry.getValue());[代码][代码] [代码][代码]}[代码][代码] [代码] [代码] [代码][代码]//打印返回内容[代码][代码] [代码][代码]int[代码] [代码]responseCode = conn.getResponseCode();[代码][代码] [代码][代码]String rescontent = [代码][代码]""[代码][代码];[代码][代码] [代码][代码]if[代码][代码]((responseCode+[代码][代码]""[代码][代码]).startsWith([代码][代码]"2"[代码][代码])){[代码][代码] [代码][代码]//成功[代码][代码] [代码][代码]rescontent = [代码][代码]new[代码] [代码]String(InputStreamTOByte(conn.getInputStream()));[代码][代码] [代码][代码]System.out.println([代码][代码]"图片上传成功:"[代码][代码]+rescontent);[代码][代码] [代码][代码]}[代码][代码]else[代码][代码]{[代码][代码] [代码][代码]//失败[代码][代码] [代码][代码]rescontent = [代码][代码]new[代码] [代码]String(InputStreamTOByte(conn.getErrorStream()));[代码][代码] [代码][代码]System.out.println([代码][代码]"图片上传失败:"[代码][代码]+rescontent);[代码][代码] [代码][代码]}[代码][代码] [代码] [代码] [代码][代码]//验证微信支付返回签名[代码][代码] [代码][代码]String Wtimestamp = responseHeader.get([代码][代码]"Wechatpay-Timestamp"[代码][代码]).get([代码][代码]0[代码][代码]);[代码][代码] [代码][代码]String Wnonce = responseHeader.get([代码][代码]"Wechatpay-Nonce"[代码][代码]).get([代码][代码]0[代码][代码]);[代码][代码] [代码][代码]String Wsign = responseHeader.get([代码][代码]"Wechatpay-Signature"[代码][代码]).get([代码][代码]0[代码][代码]);[代码][代码] [代码][代码]//拼装待签名串[代码][代码] [代码][代码]StringBuffer ss = [代码][代码]new[代码] [代码]StringBuffer();[代码][代码] [代码][代码]ss.append(Wtimestamp).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]ss.append(Wnonce).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]ss.append(rescontent).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]//验证签名[代码][代码] [代码][代码]if[代码][代码](verifyRSA(ss.toString(), Base64.decodeBase64(Wsign.getBytes()), rsaPublicKeyFile)) {[代码][代码] [代码][代码]System.out.println([代码][代码]"签名验证成功"[代码][代码]);[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]System.out.println([代码][代码]"签名验证失败"[代码][代码]);[代码][代码] [代码][代码]}[代码][代码] [代码] [代码] [代码][代码]} [代码][代码]catch[代码] [代码](Exception e) {[代码][代码] [代码][代码]System.out.println([代码][代码]"发送POST请求异常!"[代码] [代码]+ e);[代码][代码] [代码][代码]e.printStackTrace();[代码][代码] [代码][代码]}[代码] [代码] [代码][代码]}[代码][代码] [代码] [代码] [代码] [代码]}[代码]
2019-09-17 - v3图片上传接口java代码(httpclient和httpmime实现)
之前用HttpURLConnection实现的,现在改成用httpclient和httpmime实现(我用的是4.5.9版本),感觉会简单一点。水平有限,如有错误请指正。 [代码]public[代码] [代码]class[代码] [代码]uploadFileTest2 {[代码] [代码] [代码][代码]public[代码] [代码]static[代码] [代码]void[代码] [代码]main(String[] args) {[代码][代码] [代码] [代码] [代码][代码]try[代码] [代码]{[代码][代码] [代码] [代码] [代码][代码]//商户号[代码][代码] [代码][代码]String mchid = [代码][代码]""[代码][代码];[代码][代码] [代码][代码]//证书序列号[代码][代码] [代码][代码]String serial_no = [代码][代码]""[代码][代码];[代码][代码] [代码][代码]//商户私钥(拷贝apiclient_key.pem文件里-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----之间的内容[代码][代码])[代码][代码] [代码][代码]String rsaPrivateKey = [代码][代码]""[代码][代码];[代码][代码] [代码][代码]//微信支付平台公钥[代码][代码] [代码][代码]String rsaPublicKeyFile = [代码][代码]""[代码][代码]; [代码][代码] [代码][代码]//时间戳[代码][代码] [代码][代码]String timestamp = Long.toString(System.currentTimeMillis()/[代码][代码]1000[代码][代码]);[代码][代码] [代码][代码]//随机数[代码][代码] [代码][代码]String nonce_str = [代码][代码]""[代码][代码];[代码][代码] [代码] [代码] [代码][代码]//图片文件[代码][代码] [代码][代码]String filePath = [代码][代码]""[代码][代码];[代码][代码]//文件路径[代码][代码] [代码][代码]File file = [代码][代码]new[代码] [代码]File(filePath);[代码][代码] [代码][代码]String filename = file.getName();[代码][代码]//文件名[代码][代码] [代码][代码]String fileSha256 = DigestUtils.sha256Hex([代码][代码]new[代码] [代码]FileInputStream(file));[代码][代码]//文件sha256[代码][代码] [代码] [代码] [代码][代码]//拼签名串[代码][代码] [代码][代码]StringBuffer sb = [代码][代码]new[代码] [代码]StringBuffer();[代码][代码] [代码][代码]sb.append([代码][代码]"POST"[代码][代码]).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]sb.append([代码][代码]"/v3/merchant/media/upload"[代码][代码]).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]sb.append(timestamp).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]sb.append(nonce_str).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]sb.append([代码][代码]"{\"filename\":\""[代码][代码]+filename+[代码][代码]"\",\"sha256\":\""[代码][代码]+fileSha256+[代码][代码]"\"}"[代码][代码]).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]System.out.println([代码][代码]"签名原串:"[代码][代码]+sb.toString());[代码][代码] [代码] [代码] [代码][代码]//计算签名[代码][代码] [代码][代码]String sign = [代码][代码]new[代码] [代码]String(Base64.encodeBase64(signRSA(sb.toString(),rsaPrivateKey)));[代码][代码] [代码][代码]System.out.println([代码][代码]"签名sign值:"[代码][代码]+sign);[代码][代码] [代码] [代码] [代码][代码]//拼装http头的Authorization内容[代码][代码] [代码][代码]String authorization =[代码][代码]"WECHATPAY2-SHA256-RSA2048 mchid=\""[代码][代码]+mchid+[代码][代码]"\",nonce_str=\""[代码][代码]+nonce_str+[代码][代码]"\",signature=\""[代码][代码]+sign+[代码][代码]"\",timestamp=\""[代码][代码]+timestamp+[代码][代码]"\",serial_no=\""[代码][代码]+serial_no+[代码][代码]"\""[代码][代码];[代码][代码] [代码][代码]System.out.println([代码][代码]"authorization值:"[代码][代码]+authorization);[代码][代码] [代码] [代码] [代码][代码]//接口URL[代码][代码] [代码][代码]String url = [代码][代码]"https://api.mch.weixin.qq.com/v3/merchant/media/upload"[代码][代码]; [代码][代码] [代码][代码]CloseableHttpClient httpclient = HttpClients.createDefault(); [代码][代码] [代码][代码]HttpPost httpPost = [代码][代码]new[代码] [代码]HttpPost(url);[代码][代码] [代码] [代码] [代码][代码]//设置头部[代码][代码] [代码][代码]httpPost.addHeader([代码][代码]"Accept"[代码][代码], [代码][代码]"application/json"[代码][代码]);[代码][代码] [代码][代码]httpPost.addHeader([代码][代码]"Content-Type"[代码][代码], [代码][代码]"multipart/form-data"[代码][代码]);[代码][代码] [代码][代码]httpPost.addHeader([代码][代码]"Authorization"[代码][代码], authorization);[代码][代码] [代码] [代码] [代码][代码]//创建MultipartEntityBuilder [代码][代码] [代码][代码]MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create()[代码].setMode(HttpMultipartMode.RFC6532);[代码] [代码][代码]//设置boundary[代码][代码] [代码][代码]multipartEntityBuilder.setBoundary([代码][代码]""[代码][代码]);[代码][代码] [代码][代码]multipartEntityBuilder.setCharset(Charset.forName([代码][代码]"UTF-8"[代码][代码]));[代码][代码] [代码][代码]//设置meta内容[代码][代码] [代码][代码]multipartEntityBuilder.addTextBody([代码][代码]"meta"[代码][代码], [代码][代码]"{\"filename\":\""[代码][代码]+filename+[代码][代码]"\",\"sha256\":\""[代码][代码]+fileSha256+[代码][代码]"\"}"[代码][代码], ContentType.APPLICATION_JSON);[代码][代码] [代码][代码]//设置图片内容[代码][代码] [代码][代码]multipartEntityBuilder.addBinaryBody([代码][代码]"file"[代码][代码], file, ContentType.create([代码][代码]"image/jpg"[代码][代码]), filename);[代码][代码] [代码][代码]//放入内容[代码][代码] [代码][代码]httpPost.setEntity(multipartEntityBuilder.build()); [代码][代码] [代码] [代码] [代码][代码]//获取返回内容[代码][代码] [代码][代码]CloseableHttpResponse response = httpclient.execute(httpPost);[代码][代码] [代码][代码]HttpEntity httpEntity = response.getEntity(); [代码][代码] [代码][代码]String rescontent = [代码][代码]new[代码] [代码]String(InputStreamTOByte(httpEntity.getContent()));[代码][代码] [代码][代码]System.out.println([代码][代码]"返回内容:"[代码] [代码]+ rescontent);[代码][代码] [代码][代码]//获取返回的http header[代码][代码] [代码][代码]Header headers[] = response.getAllHeaders(); [代码][代码] [代码][代码]int[代码] [代码]i = [代码][代码]0[代码][代码]; [代码][代码] [代码][代码]while[代码] [代码](i < headers.length) { [代码][代码] [代码][代码]System.out.println(headers[i].getName() + [代码][代码]": "[代码] [代码]+ headers[i].getValue()); [代码][代码] [代码][代码]i++; [代码][代码] [代码][代码]} [代码][代码] [代码] [代码] [代码][代码]//验证微信支付返回签名[代码][代码] [代码][代码]String Wtimestamp = response.getHeaders([代码][代码]"Wechatpay-Timestamp"[代码][代码])[[代码][代码]0[代码][代码]].getValue();[代码][代码] [代码][代码]String Wnonce = response.getHeaders([代码][代码]"Wechatpay-Nonce"[代码][代码])[[代码][代码]0[代码][代码]].getValue();[代码][代码] [代码][代码]String Wsign = response.getHeaders([代码][代码]"Wechatpay-Signature"[代码][代码])[[代码][代码]0[代码][代码]].getValue();[代码][代码] [代码][代码]//拼装待签名串[代码][代码] [代码][代码]StringBuffer ss = [代码][代码]new[代码] [代码]StringBuffer();[代码][代码] [代码][代码]ss.append(Wtimestamp).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]ss.append(Wnonce).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]ss.append(rescontent).append([代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]//验证签名[代码][代码] [代码][代码]if[代码][代码](verifyRSA(ss.toString(), Base64.decodeBase64(Wsign.getBytes()), rsaPublicKeyFile)) {[代码][代码] [代码][代码]System.out.println([代码][代码]"签名验证成功"[代码][代码]);[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]System.out.println([代码][代码]"签名验证失败"[代码][代码]);[代码][代码] [代码][代码]}[代码][代码] [代码] [代码] [代码][代码]EntityUtils.consume(httpEntity);[代码][代码] [代码][代码]response.close();[代码][代码] [代码] [代码] [代码][代码]} [代码][代码]catch[代码] [代码](Exception e) {[代码][代码] [代码][代码]System.out.println([代码][代码]"发送POST请求异常!"[代码] [代码]+ e);[代码][代码] [代码][代码]e.printStackTrace();[代码][代码] [代码][代码]}[代码] [代码] [代码][代码]}[代码][代码] [代码] [代码] [代码][代码]public[代码] [代码]static[代码] [代码]byte[代码][代码][] InputStreamTOByte(InputStream in) [代码][代码]throws[代码] [代码]IOException{ [代码][代码] [代码] [代码] [代码][代码]int[代码] [代码]BUFFER_SIZE = [代码][代码]4096[代码][代码]; [代码][代码] [代码][代码]ByteArrayOutputStream outStream = [代码][代码]new[代码] [代码]ByteArrayOutputStream(); [代码][代码] [代码][代码]byte[代码][代码][] data = [代码][代码]new[代码] [代码]byte[代码][代码][BUFFER_SIZE]; [代码][代码] [代码][代码]int[代码] [代码]count = -[代码][代码]1[代码][代码]; [代码][代码] [代码] [代码] [代码][代码]while[代码][代码]((count = in.read(data,[代码][代码]0[代码][代码],BUFFER_SIZE)) != -[代码][代码]1[代码][代码]) [代码][代码] [代码][代码]outStream.write(data, [代码][代码]0[代码][代码], count); [代码][代码] [代码] [代码] [代码][代码]data = [代码][代码]null[代码][代码]; [代码][代码] [代码][代码]byte[代码][代码][] outByte = outStream.toByteArray();[代码][代码] [代码][代码]outStream.close();[代码][代码] [代码] [代码] [代码][代码]return[代码] [代码]outByte; [代码][代码] [代码][代码]} [代码][代码] [代码] [代码] [代码] public static byte[] signRSA(String data, String priKey) throws Exception { //签名的类型 Signature sign = Signature.getInstance("SHA256withRSA"); //读取商户私钥,该方法传入商户私钥证书的内容即可 byte[] keyBytes = Base64.decodeBase64(priKey); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); sign.initSign(privateKey); sign.update(data.getBytes("UTF-8")); return sign.sign(); } public static boolean verifyRSA(String data, byte[] sign, String pubKey) throws Exception{ if(data == null || sign == null || pubKey == null){ return false; } CertificateFactory cf = CertificateFactory.getInstance("X.509"); FileInputStream in = new FileInputStream(pubKey); Certificate c = cf.generateCertificate(in); in.close(); PublicKey publicKey = c.getPublicKey(); Signature signature = Signature.getInstance("SHA256WithRSA"); signature.initVerify(publicKey); signature.update(data.getBytes("UTF-8")); return signature.verify(sign); } [代码][代码] [代码] [代码] [代码]}[代码]
2019-09-17 - 没有违规记录,但是有实时交易涉嫌异常,申诉直接被关闭支付权限,是什么原因?
商户号:1518485721 8月中旬的时候, 收到6次风控:涉嫌实时交易异常,出现个别用户无法支付,但是情况很少,一共出现了6次。没有违约记录,咨询95017客服,客服说可以申诉一下,结果提交申诉后直接被关闭支付权限。然后继续咨询客服,得到的是违规的答复,具体哪条违规未说明,要我们自己去看协议。 我们支付环节,主要涉及到:1,用户身份证信息和个人人脸识别认证 收费10元 2,电子协议签署和存储,收费10元。 3,协议违约后帮用户代理提交仲裁服务 收费299元。 没有其他的收费环节。 我们公司的产品主要是帮用户做借款/欠款/借物过程中的电子合同签约,并且违约后提供仲裁提交服务。目的是替代线下的纸质借条/欠条和异地签约的问题。 我们仔细查看了微信商户支付的违规类目,并没有相关的违规内容。问客服,客服的回答永远是违规了支付协议,具体是什么要我们自己去看。 明示到底是违规了那条协议,我们可以整改。如果就直接被关闭支付权限,有失公允,毕竟我们后台都没收到违约记录。 后来申诉,但是每次都是直接恢复【维持原判】,所以我想问一下,我们提供产品,支付收款是违法犯罪了吗? 后来导致小程序也无法审核通过,说是涉及未开放目录,我们在小程序交流区咨询了官方,官方回复查看我们小程序后没有类目问题,请重新提交审核 以下是截图: [图片] 我的支付账户仅仅为小程序【欠条说】收款,但是为何小程序类目没问题,微信商户支付却直接被关闭权限,且给不出具体的理由,因为合约和未开放目录我们都仔细阅读了,没有违规违约的地方,也没有涉及未开放目录 以下是商户后台【违约记录】页面的截图 [图片] 申诉也没作用,每次都是很短时间就是直接驳回维持原判,也没有给出具体的理由 [图片] 团队花了几十万,历时4个月,完成小程序的开发和业务上线,而且已经稳定运营了半年时间,我们的业务非常契合小程序的场景,好友之间可以在线直接签署协议,通过微信发送就可以完成双方签约,完全免去了签署不规范的纸质协议和异地难签署的问题,并且我们还提供违约的仲裁申请协助服务。从整个业务逻辑来说完全没有违规的地方,就是电子协议的应用。 实在不清楚到底是什么愿意被关闭支付权限?我们认为微信支付在审核的时候有误判的嫌疑,在没对我们业务进行充分了解的情况下不负责任的关闭权限。虽然我们是在微信的生态下创建的产品,但是贵司毫无根据无理由的前提下关闭相关业务,对我们小公司而言,太不负责任了。这不是一个大公司的担当和态度!希望能给出明确的愿意并且给出整改意见,如果我们确实有违规的地方,我们立即整改
2019-09-24