当前位置:首页 > 其他 > 正文内容

微信小游戏sdk接入付出和登录,处理了wx小游戏内不支持ios付出的痛点

邻居的猫1个月前 (12-09)其他1094

前情概要

微信小游戏是小程序的一种。
项目接入微信小游戏sdk的付出和登录。首要难点在于接入ios的付出。由于官方只支撑android, 不支撑ios。
即ios用户不能直接在小游戏中建议付出,参阅市面上的wx小游戏,大都选用的是进入客服会话,客服发付出链接,ios玩家点击链接后拉起付出付款
wx的文档许多,但并没有在一块,本文档供给了接入wxsdk 各流程和相关链接。期望后来者接入不需求像我相同吃力。
以下一切流程我自己都是跑经过的,无需忧虑。 此文章首要侧重于服务器部分的完结, 许多难写的当地, 我也贴上了Go代码。

wx小游戏 andorid 付出流程

        图1: wx小游戏付出流程

小游戏道具直购付出成功,发货回调

  1. 文档参阅
    https://developers.weixin.qq.com/minigame/dev/guide/open-ability/virtual-payment/goods.html
    https://docs.qq.com/doc/DVUN0QWJja0J5c2x4?open_in_browser=true
    https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html
    https://docs.qq.com/doc/DR1hhWlpnQXJXWHRh
    https://docs.qq.com/doc/DVWF6Z3dEVHJPWExn

  2. 装备

       图2:wx小游戏虚拟付出装备图,url只答应装备 https 最初
    

2.1 虚拟付出 2.0 > 直购装备 > 道具发货装备 > 敞开推送。点击提交会当即向装备的url 发送Http Get恳求。 这是验证url是否可用
服务器收到Get恳求后需求校验签名, 并回来url参数中的echoStr, 才干提交装备。
不管装备的明文形式仍是安全形式,签名都按明文形式解析。 以下是Go版别的代码

点击检查小游戏 道具直购发货推送Get验签代码
func receiveMsgNotify(tx *Tx, w http.ResponseWriter, r *http.Request) {
	// 第一次会发微信sdk会发Get来验证, 实践传输数据会发post
	if r.Method == http.MethodGet {
		replyVerifyUrl(tx, w, r)
		return
	}
   // 处理post恳求
}

// replyVerifyUrl 回复敞开音讯推送时验证Url可用的Get恳求
func replyVerifyUrl(tx *Tx, w http.ResponseWriter, r *http.Request) {
	// 签名验证, 音讯是否来自微信
	query := r.URL.Query()
	signature := query.Get("signature")
	timestamp := query.Get("timestamp")
	nonce := query.Get("nonce")
	echostr := query.Get("echostr")

	if !plainTextModeVerifySignature(signature, timestamp, nonce) {
		w.Write([]byte("fail"))
	}

	// 第一次会发微信sdk会发Get来验证, 实践传输数据会发post
	w.Write([]byte(echostr))
}
// plainTextModeVerifySignature 明文形式签名验证
func plainTextModeVerifySignature(signature, timestamp, nonce string) bool {
	// 签名验证, 音讯是否来自微信
	strings := []string{timestamp, nonce, "你装备的Token"}
	sort.Strings(strings) // 进行字典型排序
	data := sha1.Sum([]byte(fmt.Sprintf("%s%s%s", strings[0], strings[1], strings[2])))

	encryptData := hex.EncodeToString(data[:])
	return encryptData == signature
}

2.2 敞开道具直购推送后,还需求点击模仿推送, 回来值 需求为 {"ErrCode":0,"ErrMsg":"Success"}, 才算装备完结 收到Post恳求 需求校验两次签名, 一次是楼上url参数中带着的签名,一次是 body中解析出来 PayEventSig字段的签名, 以下是Go版别的PayEventSig字段的验签代码 点击检查小游戏道具直购推送Payload字段验签代码
func receiveMsgNotifyPost(tx *Tx, w http.ResponseWriter, r *http.Request) bool {
    ds, _:= io.ReadAll(r.Body)
    req := &YourStructName{}
	if err = json.Unmarshal(ds, req); err != nil {     // 明文形式body里的参数能够直接解, 安全形式的解法我放在最终
		return false
	}
    payLoad := YourPayLoadStructName{}
    if err = json.Unmarshal([]byte(req.MiniGame.Payload), payLoad); err != nil {
		return false
	}
    
    var appkey string
    switch payLoad.Env {
      case 0: return 虚拟付出2.0-> 根本装备 -> 根底装备 -> 付出根底装备 -> 现网 AppKey
      case 1: return 虚拟付出2.0-> 根本装备 -> 根底装备 -> 付出根底装备 -> 沙箱 AppKey
      default: return false
    }
    createSign := createWeixinSdkSign(appkey, req.Event, req.MiniGame.Payload)
    return weixinreq.MiniGame.PayEventSig == createSign
}

// 生成微信音讯道具直购Post推送签名
func createWeixinSdkSign(app_key string, event, payload string) string {
	data := fmt.Sprintf("%s&%s", event, payload)

	hmacSha256ToHex := func(key, data string) string {
		mac := hmac.New(sha256.New, []byte(key))
		_, _ = mac.Write([]byte(data))
		bs := mac.Sum(nil)
		return hex.EncodeToString(bs[:])
	}

	return hmacSha256ToHex(app_key, data)
}

2.3 模仿发包验证成功后, 如图所示

  图3: 小程序虚拟付出道具直购推送成功敞开

wx小游戏 ios 付出流程

  图4: wx小游戏 ios 付出流程

wx小程序下单

  1. doc
    https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/mini-prepay.html
    https://github.com/wechatpay-apiv3/wechatpay-go?tab=readme-ov-file // go版别

  2. 代码
    运用官方开源库,请检查开源库供给的jaspi 下单比如。
    为什么决议用jsapi 下单, 是由于付出的时分需求用到jsapi 付出。
    为什么决议用jsapi 付出? 微信供给了 jsapi, app, h5, native, 小程序付出方法。
    app 和 小程序都需求在小程序客户端内调用, ios已知不可。
    native 是回来付款码,玩家需求扫码付出, 不方便。
    h5 和 jsapi比较, jsapi 更简略, 只需求在微信浏览器中翻开这个html就能够调起付出。故挑选jsapi下单

    图5: 微信付出的几种方法
    

服务器收到玩家进入客服会话推送

  1. 文档参阅
    https://developers.weixin.qq.com/minigame/dev/guide/open-ability/customer-message/receive.html
    https://developers.weixin.qq.com/minigame/dev/api-backend/open-api/access-token/auth.getStableAccessToken.html
    https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/kf-mgnt/kf-message/sendCustomMessage.html
    https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/kf-mgnt/kf-message/uploadTempMedia.html
    https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/kf-mgnt/kf-message/getTempMedia.html
  2. 装备
    小程序办理后台 -> 开发 -> 开发办理 -> 音讯推送装备
    和道具发货推送装备相同的, 要配Url、Token、EncodingAESKey、数据加密方法、数据格局
    url 答应装备http最初,但有必要挑选安全形式
    点击提交时,会发送Get, 以明文形式验签并回来echoStr,楼上已展现代码

服务器向玩家推送客服音讯,带着图文链接

  1. 发送客服音讯,url参数中需求带着 access_token, 它每2小时过期, 且有次数约束

  2. access_token依据appid和appsecret, 向wxsdk发送post恳求拿到。我引荐stable_token

    图5: stable_token 在有效期内屡次获取,不会使原有的token失效。
    
  3. 想要客服会话中有图片,那么需求先上传图片资源。 小程序只答应上传暂时资源,即你上传的资源3天就会过期, 过期了就需求从头上传。

Go版别上传图片资源的代码
// url := fmt.Sprintf("%s?access_token=%s&type=%s", https://api.weixin.qq.com/cgi-bin/media/upload(官网上新增图片资料的url), 从wxsdk处获取到的access_token, "image")
	
// httpUploadImage 图片过期了上传图片到wx服务器
func httpUploadImage(url, imagePath string, reply interface{}) error {
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	file, err := os.Open(imagePath)
	if err != nil {
		return fmt.Errorf("imagepath illegal:%v, err:%v", imagePath, err)
	}
	defer file.Close()

	part, err := writer.CreateFormFile("media", imagePath)
	if err != nil {
		return fmt.Errorf("createFormFile err:%v", err)
	}
	_, err = io.Copy(part, file)
	if err != nil {
		return fmt.Errorf("io.copy err:%v", err)
	}
	writer.Close()

    // 我这儿用的 "github.com/go-resty/resty/v2" 包, 用规范库的http相同的, Header 要手动改一下
	// 构建http恳求
	resp, err := resty.New().SetTimeout(5*time.Second).R().
		SetHeader("Content-Type", writer.FormDataContentType()).
		SetBody(body).
		Post(url)
	if err != nil {
		return err
	}
	if !resp.IsSuccess() {
		return fmt.Errorf("http status code: %d", resp.StatusCode())
	}
	return json.Unmarshal(resp.Body(), reply)
}

3. 上传图片后得到 media_id, 发送给玩家客服音讯中带着图文链接, url 是 game server 要供给的, thumb_url为官网上获取客服音讯中的暂时资料的url 点击检查代码
// guestSessionSendMsg 接纳玩家进入客服会话回调, 发送url
func guestSessionSendMsg(openid string, extraData *_GuestSessionExtraData) error {
	err := refreshAccessToken()  // 避免access_token过期,改写得到access_token
	if err != nil {
		return fmt.Errorf("refreshAccessToken err:%v", err)
	}
	err = refreshGuestImage()  // 避免暂时资源过期, 改写得到media_id
	if err != nil {
		return fmt.Errorf("refreshGuestImage err:%v", err)
	}

	linkTitle := "点我付出" 
	linkDescription := "充值后回来游戏检查"
	// 发给客服体系的paylink格局:https://自己的域名.com/wx_bridge/ios/paylink?sn=%s
	req := WeiXinSDKGuestSessionSendLinkReq{
		ToUser:  openid,
		MsgType: sendGuestMsgTypeLink,
		Link: &WeiXinSDKGuestSessionLink{
			Title:       linkTitle,
			Description: fmt.Sprintf("%s\n%s", "test", linkDescription),
			Url:         "game server 定制的url",
			Thumb_url:   fmt.Sprintf("%s?access_token=%s&type=image&media_id=%s", "https://api.weixin.qq.com/cgi-bin/media/get", "得到的access_token", "得到的image_id"),
		},
	}
    type _WeiXinSDKGuestSessionSendLinkRsp struct {
	  Errcode int    `json:"errcode"` // 0为成功
	  Errmsg  string `json:"errmsg"`  //
    }
	rsp := _WeiXinSDKGuestSessionSendLinkRsp{}

	dstUrl := fmt.Sprintf("%s?access_token=%s", "https://api.weixin.qq.com/cgi-bin/message/custom/send", "得到的access_token")
	if err = HttpPost(dstUrl, &req, &rsp); err != nil {
		return fmt.Errorf("HttpPost err:%v dstUrl:%v", err, dstUrl)
	}
	if rsp.Errcode != 0 {
		return fmt.Errorf("rsp not success :%v", rsp.Errmsg)
	}
	return nil
}

  图6:实操截图

玩家点击付出链接,服务器回来带小程序付出的html语法

  1. doc
    https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/jsapi-transfer-payment.html
    https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_4.shtml
  2. 代码
点击检查代码
// 收到ClickLink恳求
func ClickLink(tx *Tx, w http.ResponseWriter, r *http.Request) {
    // balabala 校验代码
	nowStr := strconv.FormatInt(time.Now().Unix(), 10)
	packageStr := fmt.Sprintf("prepay_id=%s", "下单的时分存储的prepayid")
	nonceStr := generateRandomString() // 随机字符串长度最长不能超过32位, 这段代码很简略就不贴了
	paySign, err := createSign("小程序appID", nowStr, nonceStr, packageStr) // 参阅github上付出写的
	if err != nil {
		log.Panicf("[%s]iosPayLinkcheck  createSign err:%v order sn %s", tx, err, sn)
	}

	reply := fmt.Sprintf(`<html>
<script>
function onBridgeReady() {
      WeixinJSBridge.invoke('getBrandWCPayRequest', {
          "appId": "%s",
          "timeStamp": "%s",
          "nonceStr": "%s",
          "package": "%s",
          "signType": "RSA",
          "paySign":"%s"
      },
      function(res) {
          console.log(res.err_msg)
          if (res.err_msg == "get_brand_wcpay_request:ok") { // 付出成功
                document.write("payment success");
				WeixinJSBridge.call('closeWindow');
          }
          if (res.err_msg == "get_brand_wcpay_request:fail") { // 付出失利
				document.write("payment fail");
				WeixinJSBridge.call('closeWindow');
          }
          if (res.err_msg == "get_brand_wcpay_request:cancel") { // 付出撤销
				document.write("payment cancel");
  				WeixinJSBridge.call('closeWindow');
          }
      });
  }
  if (typeof WeixinJSBridge == "undefined") {
      if (document.addEventListener) {
          document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
      } else if (document.attachEvent) {
          document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
          document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
      }
  } else {
      onBridgeReady();
  }
</script>
</html>`, WxSdkAppID, nowStr, nonceStr, packageStr, paySign)

	w.Header().Set("Content-Type", "text/html")
	w.Write([]byte(reply))
	http.Error(w, "", http.StatusOK)
}

func createSign(appid, timeStamp, nonceStr, packageStr string) (string, error) {
	message := fmt.Sprintf("%s\n%s\n%s\n%s\n", appid, timeStamp, nonceStr, packageStr)
	// 加载私钥
	privateKey, err := utils.LoadPrivateKeyWithPath("小程序商户密钥的途径") //  官方开源供给的"github.com/wechatpay-apiv3/wechatpay-go/utils"
	if err != nil {
		return "", fmt.Errorf("load private payment key err:%v", err)
	}

	// 签名
	signature, err := signWithRsa(message, privateKey)
	if err != nil {
		return "", fmt.Errorf("generateSignature err:%v", err)
	}
	return signature, nil
}

// 生成rsa签名
func signWithRsa(data string, privateKey *rsa.PrivateKey) (string, error) {
	// 运用 SHA256 对待签名数据进行哈希
	hash := sha256.New()
	hash.Write([]byte(data))
	hashed := hash.Sum(nil)

	// 运用私钥对哈希值进行 RSA 签名
	signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
	if err != nil {
		return "", fmt.Errorf("failed to sign data: %v", err)
	}

	// 将签名进行 Base64 编码
	encodedSignature := base64.StdEncoding.EncodeToString(signature)
	return encodedSignature, nil
}

小程序付出成功,发货成功回调

总算走到这一步了!
https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/payment-notice.html
相同, 运用官方开源库及比如书写
至此,wx小游戏ios付出已通

wx小游戏登录流程

   图7: wx 小游戏官方登录流程图

    图8: wx小游戏登录流程图(简化版)

跋文

推送音讯,假如运用安全形式, 官方并没有go版别的文档, 问了客服,也迟迟没回复,所以我自己写了一版。测试过能解析,但仍是有点忧虑或许也有解析不到的特别数据。

点击检查代码
// getMsgByPlainTextMode (安全)加密形式解析音讯   官方没有供给go的写法,自己写的,或许有问题。
func getMsgBySafeMode(r *http.Request, req interface{}) error {
	query := r.URL.Query()
	signature := query.Get("msg_signature")
	timestamp := query.Get("timestamp")
	nonce := query.Get("nonce")

	ds, err := io.ReadAll(r.Body)
	if err != nil {
		return fmt.Errorf("io.ReadAll err %v", err)
	}
	type _WeiXinSDKGuestMsgSafeMode struct {
		ToUserName string // 小游戏原始ID
		Encrypt    string // 密文
	}

	encryptReq := &_WeiXinSDKGuestMsgSafeMode{}
	if err = json.Unmarshal(ds, encryptReq); err != nil {
		return fmt.Errorf("json.unmarshal err:%v ds:%v", err, string(ds))
	}
	if len(encryptReq.Encrypt) == 0 {
		return fmt.Errorf("encryReq.Encrypt is empty ")
	}

	if !safeModeVerifySignature(signature, timestamp, nonce, encryptReq.Encrypt) {
		log.Errorf("getMsgByPlainTextMode signature not match, signature:%v, timestamp:%v nonce:%v", signature, timestamp, nonce)
		return errors.New("signature not match")
	}

	// 绵长的解密过程
	encodingAESKey := WxSdkGuestMsgEncodingAESKey
	encodingAESKey += "="
	aesKey, err := base64.StdEncoding.DecodeString(encodingAESKey)
	if err != nil {
		return err
	}

	tmpMsg, err := base64.StdEncoding.DecodeString(encryptReq.Encrypt)
	if err != nil {
		return err
	}

	// 运用 AES 解密
	fullStr, err := aesDecryptCBC(tmpMsg, aesKey)
	if err != nil {
		return err
	}
	msg := fullStr[20:]
	ret := strings.Split(string(msg), "}")
	if len(ret) == 0 {
		return errors.New("msg is empty")
	}
	ret[0] += "}"

	if err = json.Unmarshal([]byte(ret[0]), req); err != nil {
		return fmt.Errorf("json.unmarshal err:%v ds:%v", err, string(ds))
	}
	return nil
}

func aesDecryptCBC(cipherText, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("failed to create AES cipher: %v", err)
	}

	// 获取 AES 块巨细
	blockSize := block.BlockSize()

	// 确保密文长度是块巨细的整数倍
	if len(cipherText)%blockSize != 0 {
		return nil, fmt.Errorf("ciphertext is not a multiple of block size")
	}

	// 运用 CBC 形式解密
	mode := cipher.NewCBCDecrypter(block, key[:blockSize]) // CBC 形式,IV 是密钥的一部分
	plainText := make([]byte, len(cipherText))
	mode.CryptBlocks(plainText, cipherText)

	// 去除填充
	plainText, err = pkcs7UnPadding(plainText)
	if err != nil {
		return nil, fmt.Errorf("failed to remove padding: %v", err)
	}

	return plainText, nil
}

// 去掉 PKCS#7 填充
func pkcs7UnPadding(data []byte) ([]byte, error) {
	length := len(data)
	if length == 0 {
		return nil, fmt.Errorf("data is empty")
	}

	// 获取填充字节的巨细
	padding := int(data[length-1])
	if padding > length {
		return nil, fmt.Errorf("invalid padding")
	}

	return data[:length-padding], nil
}

扫描二维码推送至手机访问。

版权声明:本文由51Blog发布,如需转载请注明出处。

本文链接:https://www.51blog.vip/?id=688

分享给朋友:

“微信小游戏sdk接入付出和登录,处理了wx小游戏内不支持ios付出的痛点” 的相关文章

log4j2 变量注入缝隙(CVE-2021-44228)

log4j2 变量注入缝隙(CVE-2021-44228)

log4j2 JNDI注入缝隙(CVE-2021-44228) 概述 本文十分具体的自始至终debug了CVE-2021-44228缝隙的运用进程,喜爱的师傅记住点个引荐~ Apache Log4j2是一个依据Java的日志记载东西。该东西重写了Log4j结构,并且引入了许多丰厚的特性。该日志结构被...

云计算啥意思, 云计算是什么?

云计算啥意思, 云计算是什么?

云计算是一种基于互联网的计算方式,通过这种方式,共享的软硬件资源和信息可以按需提供给计算机和其他设备。云计算的核心思想是将大量用网络连接的计算资源统一管理和调度,构成一个计算资源池向用户按需服务。这种服务可以是IT和软件、互联网相关的,也可以是任意其他的服务。云计算通常涉及通过互联网来提供动态易扩展...

区块链中心化,区块链中心化的挑战与机遇

区块链技术本身是一个分布式账本技术,它旨在通过去中心化的方式记录和验证交易,从而提高透明度和安全性。区块链系统在实施过程中可能会出现不同程度的中心化现象,这取决于具体的应用场景、技术实现和治理结构。中心化在区块链中的表现形式可能包括:1. 矿池中心化:在加密货币挖矿过程中,矿池通过集合多个矿工的计算...

区块链是什么时候出现的,区块链的起源与发展历程

区块链技术最早出现在2008年,由一位或多位化名为中本聪(Satoshi Nakamoto)的人或团队在发布的比特币白皮书中首次提出。2009年,比特币作为第一个基于区块链技术的应用正式上线运行。区块链是一种分布式数据库技术,它允许数字信息在多个节点之间安全地存储和共享,同时确保数据的一致性和不可篡...

紫云1000云计算机,袩袝袪袛袨小袩袨袪袧袨啸啸啸hd

紫云1000云计算机,袩袝袪袛袨小袩袨袪袧袨啸啸啸hd

紫云1000是中国自主研发的首台“云计算机”,也是全球首台云计算机。它由紫光股份有限公司技术团队经过近两年的努力研发成功,采用与个人计算机和超级计算机完全不同的分布式体系架构,借助于云计算的虚拟化技术,由多个成本相对较低的计算资源融合而成,具有强大的计算能力。 主要特点1. 高性能:单台“紫云100...

区块链的共识机制,深入解析区块链的共识机制

区块链的共识机制,深入解析区块链的共识机制

区块链的共识机制是指通过特定的算法和规则,让网络中的节点就交易的有效性达成一致,从而保证区块链网络的安全性和可靠性。不同的区块链系统采用了不同的共识机制,常见的共识机制包括:1. 工作量证明(Proof of Work,PoW):这是比特币最早使用的共识机制。节点通过解决计算难题来竞争记账权,计算难...