服务端 API

更新时间:
复制为 MD 格式

简介

小游戏服务端接入 mPaaS 内容魔方,需分别接入 查询订单状态订单状态回调 两个接口。

订单状态回调

为确保服务端能及时接收支付结果,请按照下述规范接入 订单状态回调 接口。当用户支付成功后,系统将主动调用该接口通知商户后端。

重要
  • 若回调失败,系统将在 24 小时内进行 3~10 次重试。

  • 由于网络波动等不可控因素,回调无法保证 100% 触达。强烈建议商户后端在收到回调后,或作为兜底方案,主动调用 查询订单状态 接口以确认最终订单状态。

查询订单状态

此为服务端主动发起的查询接口,用于弥补回调可能丢失的情况,确保支付状态最终一致。

接入前提

  • 拥有阿里云账号并开通 mPaaS 相关权限。

  • 在服务端集成对应的 SDK。

最佳实践
建议商户后端采用定时轮询或在未收到回调时触发查询,以主动获取订单最终状态,保障业务稳定性。

账号前置准备

  1. 业务接入确认

    • 若当前仅考虑接入 订单状态回调,请直接参见 服务端接口 说明部分,忽略后续步骤。

    • 若需要接入 查询订单状态,请继续参考后续步骤进行 AccessKeyId/AccessKeySecret 配置。

  2. RAM 用户创建

    请确保您已创建阿里云账号,并成功登录阿里云 RAM 控制台身份管理 > 用户 > 创建用户,进行用户创建,必须勾选 使用永久 AccessKey 访问

    image

  3. AK/SK 保存

    创建完成后,仅首次创建,会出现 AccessKey 和 AccessSecret 相关信息,务必复制保存!

    image.png

  4. 申请 mPaaS 权限

    在阿里云 RAM 控制台,权限管理 > 授权,授权主题选择刚创建的 RAM 用户,权限策略选择 AliyunMPAASFullAccess,新增 mPaaS 接入权限。

    image.png

  5. AK/SK 使用

    目前仅查询订单状态,需主动接入 SDK 依赖后,使用 AK/SK,访问 mPaaS 服务进行订单状态主动查询。

    说明

    整体详细流程可参考 对 RAM 账号进行应用级别的访问控制,在 创建 AccessKey(请前往阿里云工作台进行创建)中可查看 AccessKeyId 与 AccessKeySecret 的具体含义及使用方式 。

服务端接口

订单状态回调

接口说明

游戏商服务端需主动实现该 HTTP 接口,供 mPaaS 调用以接收订单状态变更通知。接口定义包含路径、请求方式、参数及返回结果。

项目

说明

请求方式

POST

接口路径

/notify/order/status

Content-Type

application/x-www-form-urlencoded

超时时间

5 秒

签名位置

URL 参数 sign,具体签名信息,可参考 签名算法

入参

参数

类型

是否必选

示例

描述

custom_id

String

ORDER_20260408_001

CP 订单号,CP 侧唯一标识。

trade_no

String

2026040800123456

业务订单号,平台侧订单号。

open_id

String

8921198212819

开放平台用户 ID。

cp_extra

String

{"order_status":"ORDER_SUCCESS"}

扩展参数,JSON 格式,包含 order_status 等业务信息。

sign

String

A1B2C3D4E5F6...

签名值,拼接在 URL 参数中,详见 签名算法

cp_extra 扩展字段说明

字段

类型

是否必选

示例

描述

order_status

String

ORDER_SUCCESS

订单状态:

  • ORDER_CREATED

    订单已创建,等待付款

  • ORDER_SUCCESS

    订单已付款/已发货/交易成功

  • ORDER_FAILED

    交易失败/交易关闭

amount

Integer

100

金额(单位:分)

出参

参数

类型

是否必选

示例

描述

response

Object

见下方 示例

响应体,包含 code 和 msg。

response.code

String

10000

结果码

response.msg

String

Success

结果信息

response.sub_code

String

SYSTEM_ERROR

业务错误码(失败时必选)

response.sub_msg

String

系统繁忙

业务错误描述(失败时必选)

sign

String

A1B2C3D4E5F6...

签名值

response.code 结果码说明

结果码

结果信息

描述

10000

Success

处理成功

40004

Business Failed

业务处理失败

重要
  • 错误码只支持 10000(成功)和 40004(失败),其他值会被视为非法响应。

  • sub_code 和 sub_msg 为可选参数,仅在业务失败时返回,且值不能为空。

  • CP 需在收到通知后 5 秒内 返回响应,否则视为通知失败。

  • 返回 code=10000 表示处理成功,平台不再发送通知。

  • 返回 code=40004 或超时未响应,平台会进行重试。

出参示例

成功

{
    "response": {
        "code": "10000",
        "msg": "Success"
    },
    "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
}

失败

{
    "response": {
        "code": "40004",
        "msg": "Business Failed",
        "sub_code": "SYSTEM_ERROR",
        "sub_msg": "系统繁忙"
    },
    "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
}

查询订单状态

请优先完成 SDK 接入,具体步骤请参考 SDK 调用示例 - SDK 依赖 章节。

重要

代码示例仅用于展示参数结构,不建议直接集成到业务代码中。

接口说明

面向游戏服务端,在 订单创建支付成功 后,需主动调用此接口以核实用户订单状态。

入参

QueryPayOrderToMsenceRequest

参数

类型

是否必选

描述

miniProgramId

String

Y

小程序 ID

platformId

String

Y

平台 ID,例如:

  • 咸鱼:mPaaS_Goosefish

  • 优酷:mPaaS_YOUKU

customId

String

Y

客户订单号

出参

参数

类型

描述

orderStatus

String

  • 1 - 订单已创建

  • 2 - 订单交易支付成功

  • 3 - 订单交易支付失败

requestId

String

公共出参,请求 ID

success

boolean

公共出参,是否请求成功

resultCode

String

公共出参,结果码

resultMsg

String

公共出参,结果信息

resultCode 结果码说明

结果码

结果信息

备注

200

SUCCESS

成功

1001

ILLEGAL_ARGUMENTS

参数错误

1002

MINI_GAME_UNREGISTER

小程序游戏未注册

1003

ORDER_NOT_EXIST

订单不存在

1004

REGISTER_INFO_INCOMPLETE

小程序注册信息不完整

1005

RPC_EXCEPTION

调用第三方系统异常

1006

RPC_RESULT_FAIL

调用第三方服务结果失败

9999

SYSTEM_EXCEPTION

系统异常

出参示例

{
    "RequestId": "2240a4ee90a64ad1a61cf64676861a08",
    "Success": true,
    "ResultCode": "200",
    "ResultMsg": "SUCCESS",
    "MpaasUserGamecenterPaymentQuerystatusResponse": {
        "orderStatus": "1"
    }
}

签名算法

为确保链路的安全性与可靠性,mPaaS 在调用订单状态回调时会对参数进行签名。商家服务端需对回调参数执行签名验证。下文将详细介绍 mPaaS 的签名机制及验证方案。

签名规则

采用 MD5 签名,与支付宝签名算法保持一致。签名结果拼接到 URL 参数中。

步骤

说明

步骤 1

过滤空值参数和 sign 参数。

步骤 2

将剩余参数按字母升序排列。

步骤 3

拼接成 key1=value1&key2=value2... 格式。

步骤 4

在末尾拼接密钥:&key=密钥

步骤 5

进行 MD5 加密,转大写。

签名示例

原始参数

 custom_id=ORDER_20260408_001
 trade_no=2026040800123456
 open_id=8921198212819
 cp_extra={"order_status":"ORDER_SUCCESS"}

排序后拼接

 cp_extra={"order_status":"ORDER_SUCCESS"}&custom_id=ORDER_20260408_001&open_id=8921198212819&trade_no=2026040800123456

拼接密钥(假设密钥为 abc123)

 cp_extra={"order_status":"ORDER_SUCCESS"}&custom_id=ORDER_20260408_001&open_id=8921198212819&trade_no=2026040800123456&key=abc123

MD5 加密结果

 A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6

回调 URL 示例

POST /notify/order/status?sign=A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6
Content-Type: application/x-www-form-urlencoded

custom_id=ORDER_20260408_001&trade_no=2026040800123456&open_id=8921198212819&cp_extra=%7B%22order_status%22%3A%22ORDER_SUCCESS%22%7D

签名工具类示例

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.*;

/**
 * 通知签名工具类
 * @author chenweiyue.cwy
 * @version $Id: NotifySignUtil.java, v 0.1 2026-04-09 10:00 chenweiyue.cwy Exp $$
 */
public class NotifySignUtil {

    /**
     * 生成签名
     * @param params 参数Map
     * @param secret 签名密钥
     * @return 签名值
     */
    public static String generateSign(Map<String, String> params, String secret) {
        if (params == null || params.isEmpty() || StringUtils.isBlank(secret)) {
            return null;
        }

        // 1. 过滤空值并排序
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);

        // 2. 拼接字符串
        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            String value = params.get(key);
            if (StringUtils.isNotBlank(value) && !"sign".equals(key)) {
                sb.append(key).append("=").append(value).append("&");
            }
        }
        sb.append("key=").append(secret);

        // 3. MD5加密,转大写
        return DigestUtils.md5Hex(sb.toString()).toUpperCase();
    }

    /**
     * 验证签名
     * @param params 参数Map
     * @param sign 签名值
     * @param secret 签名密钥
     * @return 验签是否通过
     */
    public static boolean verifySign(Map<String, String> params, String sign, String secret) {
        if (StringUtils.isBlank(sign) || StringUtils.isBlank(secret)) {
            return false;
        }

        String calculatedSign = generateSign(params, secret);
        return sign.equals(calculatedSign);
    }

    /**
     * 从请求参数中构建签名Map(排除sign参数)
     * @param params 原始参数Map
     * @return 用于签名的Map
     */
    public static Map<String, String> buildSignParams(Map<String, String[]> params) {
        Map<String, String> signParams = new HashMap<>();
        if (params == null || params.isEmpty()) {
            return signParams;
        }

        for (Map.Entry<String, String[]> entry : params.entrySet()) {
            String key = entry.getKey();
            if ("sign".equals(key)) {
                continue;
            }

            String[] values = entry.getValue();
            if (values != null && values.length > 0) {
                StringBuilder valueStr = new StringBuilder();
                for (int i = 0; i < values.length; i++) {
                    if (i > 0) {
                        valueStr.append(",");
                    }
                    valueStr.append(values[i]);
                }
                signParams.put(key, valueStr.toString());
            }
        }

        return signParams;
    }

    /**
     * 生成响应签名
     * @param responseMap 响应内容Map
     * @param secret 签名密钥
     * @return 签名值
     */
    public static String generateResponseSign(Map<String, Object> responseMap, String secret) {
        if (responseMap == null || responseMap.isEmpty() || StringUtils.isBlank(secret)) {
            return null;
        }

        // 将Object值转换为String
        Map<String, String> stringParams = new HashMap<>();
        for (Map.Entry<String, Object> entry : responseMap.entrySet()) {
            if (entry.getValue() != null) {
                stringParams.put(entry.getKey(), String.valueOf(entry.getValue()));
            }
        }

        return generateSign(stringParams, secret);
    }
}

SDK 调用示例

SDK 依赖

Java

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>mpaas20201028</artifactId>
    <version>5.2.0</version>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>tea-openapi</artifactId>
    <version>0.3.10</version>
</dependency>

Go

go get github.com/alibabacloud-go/mpaas-20201028/v3

PHP

composer require alibabacloud/mpaas-20201028

代码示例

示例代码仅供参考,请勿直接用于生产环境接入。如有任何疑问,欢迎搜索群号 145930007362 加入钉钉群进行咨询交流。

Java

public static String REGION_ID = "cn-hangzhou";
public static String ENDPOINT = "mpaas.cn-hangzhou.aliyuncs.com";

// 杭州非金预发
public static String ACCESS_KEY_ID = "AK";
public static String ACCESS_KEY_SECRET = "AS";
private static String MINI_PROGRAM_ID = "小程序ID";
private static String PLATFORM_ID = "平台ID";
public static String CUSTOM_ID = "订单自定义ID";


public static void main(String[] args) throws Exception {
    Config config = new Config();
    // 必填,您的 AccessKey ID
    config.setAccessKeyId(MPAAS_AK_ENV);
    // 必填,您的 AccessKey Secret
    config.setAccessKeySecret(MPAAS_SK_ENV);
    // mPaaS 的 REGION_ID 和 Endpoint,以杭州非金为例
    config.setRegionId(REGION_ID);
    config.setEndpoint(ENDPOINT);
    Client client = new Client(config);

    // 调用保存订单关系信息接口
    saveOrderRelationinfo(client);
}

private static void queryPayOrderToMsence(Client client) throws Exception {
    QueryPayOrderToMsenceRequest request = new QueryPayOrderToMsenceRequest();
    request.setMiniProgramId(MINI_PROGRAM_ID);
    request.setPlatformId(PLATFORM_ID);
    request.setCustomId(CUSTOM_ID);

    QueryPayOrderToMsenceResponse response = client.queryPayOrderToMsence(request);
    System.out.println("response==>" + JSON.toJSONString(response));
}

Go

package main

import (
	"fmt"
	"os"

	openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
	mpaas "github.com/alibabacloud-go/mpaas-20201028/v3/client"
	"github.com/alibabacloud-go/tea/dara"
)

func main() {
	if err := queryPayOrderToMsence(); err != nil {
		fmt.Fprintf(os.Stderr, "查询订单失败: %v\n", err)
		os.Exit(1)
	}
}

// queryPayOrderToMsence 演示如何调用 QueryPayOrderToMsence 接口
// 该接口用于查询支付订单状态
func queryPayOrderToMsence() error {
	// 1. 创建客户端配置
	config := &openapi.Config{
		// 阿里云 AccessKey ID
		AccessKeyId: dara.String("LTAI5tMZR3xxx"),
		// 阿里云 AccessKey Secret
		AccessKeySecret: dara.String("C811bjRc8z9HxeI0Nxxx"),
		// 区域 ID,例如:cn-hangzhou
		RegionId: dara.String("cn-hangzhou"),
		// 可选:自定义 endpoint
		Endpoint: dara.String("mpaas.cn-hangzhou.aliyuncs.com"),
	}

	// 2. 创建客户端
	client, err := mpaas.NewClient(config)
	if err != nil {
		return fmt.Errorf("创建 mpaas 客户端失败: %w", err)
	}

	// 3. 构建请求参数
	request := &mpaas.QueryPayOrderToMsenceRequest{
		// 平台标识
		PlatformId: dara.String("mPaaS_Goosefish"),
		// 小程序 ID
		MiniProgramId: dara.String("123321"),
		// 自定义 ID(与创建订单时传入的 CustomId 一致)
		CustomId: dara.String("test_custom_id"),
	}

	// 4. 调用接口
	response, err := client.QueryPayOrderToMsence(request)
	if err != nil {
		return fmt.Errorf("调用 QueryPayOrderToMsence 失败: %w", err)
	}

	// 5. 处理响应
	if response.Body != nil {
		fmt.Printf("请求 ID: %s\n", dara.StringValue(response.Body.RequestId))
		fmt.Printf("是否成功: %v\n", dara.BoolValue(response.Body.Success))
		fmt.Printf("结果码: %s\n", dara.StringValue(response.Body.ResultCode))
		fmt.Printf("结果消息: %s\n", dara.StringValue(response.Body.ResultMsg))

		if response.Body.MpaasUserGamecenterPaymentQuerystatusResponse != nil {
			fmt.Printf("订单状态: %s\n", dara.StringValue(response.Body.MpaasUserGamecenterPaymentQuerystatusResponse.OrderStatus))
		}
	}

	return nil
}

PHP

<?php

require_once __DIR__ . '/../vendor/autoload.php';

use AlibabaCloud\SDK\MPaaS\V20201028\MPaaS;
use AlibabaCloud\SDK\MPaaS\V20201028\Models\QueryPayOrderToMsenceRequest;
use AlibabaCloud\Dara\Exception\DaraException;
use AlibabaCloud\Dara\Models\RuntimeOptions;
use Darabonba\OpenApi\Models\Config;

// 1. 创建客户端配置
$config = new Config([
    'accessKeyId' => getenv('ALIBABA_CLOUD_ACCESS_KEY_ID') ?: 'LTAI5tMZR33xxx',
    'accessKeySecret' => getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET') ?: 'C811bjRc8z9HxeIxxx',
    'regionId' => 'cn-hangzhou',
    'endpoint' => 'mpaas.cn-hangzhou.aliyuncs.com',
]);

// 2. 创建客户端实例
$client = new MPaaS($config);

// 3. 构建请求参数
$request = new QueryPayOrderToMsenceRequest([
    'customId' => 'your-custom-id',     // 自定义ID
    'miniProgramId' => 'your-mini-program-id', // 小程序ID
    'platformId' => 'your-platform-id', // 平台ID
]);

// 4. 创建运行时配置(可选)
$runtime = new RuntimeOptions([
    'readTimeout' => 10000,
    'connectTimeout' => 5000,
]);

try {
    // 方式一:使用默认运行时配置调用
    $response = $client->queryPayOrderToMsence($request);

    // 方式二:使用自定义运行时配置调用
    // $response = $client->queryPayOrderToMsenceWithOptions($request, $runtime);

    // 5. 处理响应
    $body = $response->body;
    echo "RequestId: " . $body->requestId . "\n";
    echo "ResultCode: " . $body->resultCode . "\n";
    echo "ResultMsg: " . $body->resultMsg . "\n";
    echo "Success: " . ($body->success ? 'true' : 'false') . "\n";

    // 获取订单状态
    if ($body->mpaasUserGamecenterPaymentQuerystatusResponse) {
        echo "OrderStatus: " . $body->mpaasUserGamecenterPaymentQuerystatusResponse->orderStatus . "\n";
    }

} catch (DaraException $e) {
    // 处理业务异常
    echo "DaraException: " . $e->getMessage() . "\n";
    echo "Code: " . $e->getCode() . "\n";
} catch (\Exception $e) {
    // 处理其他异常
    echo "Exception: " . $e->getMessage() . "\n";
}