# 支付成功通知

  • 玩家下单支付成功后,美团开放平台会回调游戏开发者服务端的回调接口(开发者提供的回调接口的端口号,必须是80或者443),完成支付结果通知。
  • 如果游戏开发者服务端返回异常,美团开放平台会进行重试,重试间隔采取递增的策略。
  • 回调说明 : 接口回调采取对称加密的方式,假设开发者在游戏开放平台注册的支付回调接口为http://url.callback;则回调时形式如:
    method:POST
    header:application/json;charset=utf-8
    url:http://url.callback
    postdata:
    {
      data:加密后的数据,
      sign:签名
    }
    
  • 开发者的服务端在接收到回调时,可以使用sign的算法进行签名验证。如果匹配了,说明来自于美团游戏开放平台的接口回调是正确的,开发者可以进一步解密数据包和执行业务逻辑。

# 支付成功回调开发者接口

  • 通知接口参数:

    参数名 类型 默认值 是否必填 说明
    sign String 是 签名,签名方法见附录
    data String 是 加密后的数据,通知内容(JSON)加密后的生成的字符串,解密方法见附录
  • data 解密后的内容:

    参数名 类型 默认值 是否必填 说明
    bizOrderId String 是 游戏厂商下单传入的订单号
    mgcOrderId String 是 美团侧订单号
    mgcId String 是 美团侧玩家角色号
    price String 是 美团侧收到的游戏下单金额,单位:分.
    payStatus String 是 OK:支付成功
  • 开发者服务端接口收到回调通知后,接口响应(code:0代表成功,非0代表失败):

    { 
      "code": 0, 
      "msg": "ok"
    }
    

# 附录

  • Sign签名的计算算法为
    String secretKey = AESUtil.createKey(appId + "&" + appSecret);  //开发者可以在开发者后台查询得到开放平台为其分配的appId和appSecret
    signature = SHAUtil.encryptSHA1Str(data + secretKey)   //data :美团开发平台回调返回的报文中data字段对应的value                                                                                                  
    
  • AESUtil工具类
    import java.security.MessageDigest;
    import javax.crypto.SecretKey;
    
    public static String createKey(String password) {
            try {
                byte[] key = password.getBytes("UTF-8");
                MessageDigest sha = MessageDigest.getInstance("SHA-1");
                key = sha.digest(key);
                key = Arrays.copyOf(key, 32);
                SecretKey secretKey = new SecretKeySpec(key, "AES");
                return Base64.getEncoder().encodeToString(secretKey.getEncoded());
            } catch (Exception var3) {
                throw new RuntimeException(var3);
            }
        }
    
  • SHAUtil工具类
    import java.nio.charset.StandardCharsets;
    import java.security.MessageDigest;
    import org.apache.commons.codec.digest.DigestUtils;
    
    public static String encryptSHA1Str(String text) {
            byte[] bytes = encryptSHA(text, "SHA-1");
            return byte2hex(bytes);
    }
    
    private static byte[] encryptSHA(String text, String algorithm) {
            MessageDigest md;
            try {
                md = MessageDigest.getInstance(algorithm);
            } catch (NoSuchAlgorithmException var4) {
                throw new IllegalArgumentException(var4);
            }
    
            byte[] infoBytes = text.getBytes(StandardCharsets.UTF_8);
            md.update(infoBytes);
            return md.digest();
    }
    
    private static String byte2hex(byte[] bytes) {
            StringBuilder stringBuilder = new StringBuilder();
            byte[] var2 = bytes;
            int var3 = bytes.length;
    
            for(int var4 = 0; var4 < var3; ++var4) {
                byte b = var2[var4];
                String hex = Integer.toHexString(b & 255);
                if (hex.length() == 1) {
                    stringBuilder.append("0");
                }
    
                stringBuilder.append(hex);
            }
    
            return stringBuilder.toString();
    }
    
  • data字段的加密使用AES_256_GCM算法加密,解密工具类如下
  • 解密工具类
    以下代码为AES_256_GCM的解密java示例代码,仅供参考
    import javax.crypto.Cipher;
    import javax.crypto.spec.GCMParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.nio.charset.StandardCharsets;
    import java.util.Arrays;
    import java.util.Base64;
    
    private static final String ALGORITHM = "AES";
    private static final String ALGORITHM_PADDING = "AES/GCM/NoPadding";
    private static final int GCM_NONCE_LENGTH_BIT = 128;
    private static final int GCM_TAG_LENGTH_BIT = 128;
    
    /**
         * 解密方法
         * @param key 密钥(AESUtil.createKey(appId + "&" + appSecret))
         * @param encryptData 密文数据
         * @return 明文数据
     */
    public static String decryptWithAESGCM256(String key, String encryptData) {
    
            try {
                byte[] decodeData = Base64.getUrlDecoder().decode(encryptData);
                byte[] nonceBytes = Arrays.copyOfRange(decodeData, 0, GCM_NONCE_LENGTH_BIT / 8);
                byte[] contentBytes = Arrays.copyOfRange(decodeData, GCM_NONCE_LENGTH_BIT / 8, decodeData.length);
                byte[] keyBytes = Base64.getDecoder().decode(key.getBytes(StandardCharsets.UTF_8));
                
                SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM);
    
                GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH_BIT, nonceBytes);
    
                Cipher cipher = Cipher.getInstance(ALGORITHM_PADDING, "SunJCE");
                cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
    
                return new String(cipher.doFinal(contentBytes), StandardCharsets.UTF_8);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
    }
    
  • PHP demo(版本使用7.1以上)
/**
    * 生成密钥
    * $appId:开发者后台看
    * $appSecret:开发者后台看
 */
function createKey($appId, $appSecret) {
	$aaa = base64_encode(str_pad(sha1($appId.'&'.$appSecret,true), 32,pack('V', 0)));
	return $aaa;
}

/**
    * 签名sha1算法
    * $secretKey: 由上面的 createKey方法生成
    * $encryptData 通知报文的data密文内容
 */
function encryptSHA1Str($secretKey, $encryptData) {
	$aaa = $encryptData.$secretKey;
	$sign = sha1($aaa);
	return $sign;
}

/**
    * AES-GCM-256解密算法
    * $secretKey: 由上面的 createKey方法生成
    * $encryptData 通知报文的data密文内容
*/
function decryptWithAESGCM256($secretKey, $encryptData) {
	$decodeEncrypt = urlsafe_b64decode($encryptData);
	$decodeSecret = base64_decode($secretKey);
	$data = openssl_decrypt(substr($decodeEncrypt, 16, -16), 'aes-256-gcm', $decodeSecret, 1, substr($decodeEncrypt, 0, 16), substr($decodeEncrypt, -16, 16));
	return $data;
}


function urlsafe_b64decode($string) {
	$data = str_replace(array('-','_'),array('+','/'),$string);
	$mod4 = strlen($data) % 4;
	if ($mod4) {
		$data .= substr('====', $mod4);
	}
	return base64_decode($data);
}
  • JS demo
// 使用示例:获取签名和解密报文
const password = `${appid}&${appsecret}`;
const secretKey = getSecretKey(password);
const encodeSignature = getEncryptSha1Str(encodeData, secretKey);
const decode = getDecryptRes(secretKey, encodeData);

import crypto from 'crypto';

/**
 * 生成密钥
 * $password:  appId + "&" + appSecret 拼接生成
 */
export function getSecretKey(password) {
    let key = crypto.createHash('sha1').update(password)
        .digest();
    key = Buffer.concat([Buffer.from(key, 0, 32), Buffer.alloc(32, 0)]).slice(0, 32);
    const base64String = Buffer.from(key).toString('base64');
    return base64String;
}

/**
 * 签名sha1算法
 * $encryptData:密文内容
 * $secretKey:密钥,由上面的createKey方法生成
 */
export function getEncryptSha1Str(encrpyData, secretKey) {
    const res = crypto.createHash('sha1').update(encrpyData + secretKey)
        .digest('hex');
    return res;
}

/**
 * aes-256-gcm 解密
 * @param {String} serectKey 密钥 base64
 * @param {String} encrpyData 密文 base64
 * @returns {string}
 */
export function getDecryptRes(serectKey, encrpyData) {
    try {
        const decodeSecretKey = Buffer.from(serectKey, 'base64');
        const decodeEncrpyData = Buffer.from(encrpyData, 'base64');
        const iv = decodeEncrpyData.slice(0, 16);
        const contentData = decodeEncrpyData.slice(16, -16);
        const decipher = crypto.createDecipheriv('aes-256-gcm', decodeSecretKey, iv);
        const decrypted = decipher.update(contentData, 'base64', 'utf8');
        return decrypted;
    } catch (e) {
        console.log('decrypt error', e);
    }
}
上次更新: 8/10/2026, 5:36:37 PM