本文档介绍了联想应用联运sdk接入操作指南,您可在了解文档内容后,自行接入应用联运sdk。
1. 接入前准备
1. 请先与联想商务达成合作意向。
2. 联系联想运营,提供应用和公司信息,并获取商户id、app id、key(公私钥、用于登录和支付校验)、SDK包。
3. 以下为需提供的信息:
1) 商户名称(例:xxx公司)
2) 应用名称
3) 应用图标(建议像素:36*36,png格式
2. 联运SDK接入须知
应用联运支持SDK目前支持版本为windows10及以上版本,具体信息请与我司商务进行咨询。
3. PC端SDK及接入文档下载地址:
下载地址:联想乐云
(密码:ksk3)
4. 简介
4.1. 阅读对象说明
研发工程师、测试工程师、产品经理
4.2. 接入订阅服务流程
1. 联系联想商业化运营,提供应用和公司信息,获取商户id、appid、key(公私钥、用于登录和支付校验)、PC端订阅SDK下载地址
2. 接入PC端订阅SDK (登录+支付)
3. 接入退款API(可选)
4.3. 名词解释
名词 | 解释说明 |
订阅SDK | 指本SDK,由联想提供,用于接入联想的各种基础服务 |
接入方 | 指接入该订阅SDK基础服务的各种三方应用 |
LID | LenovoID,即用户登录联想账号后生成的唯一用户ID |
单点登录登出 | 使用该订阅SDK的不同应用,在同一台设备上会保持相同的登录状态:A应用登录LID账号后,B应用的LID账号也是登录状态的;B应用登出后,A应用也是登出状态 |
自动登录 | 接入方应用登录后,关闭该应用,登录状态会被保留在设备本地,下次应用再启动后,通过调用接口 LYSDK_LidGetLoginStatus 实现自动登录 |
应用私钥 | 商户向订阅服务端发起请求(查询、退款等API接口),进行签名;订阅服务端使用应用公钥验签 |
平台公钥 | 订阅服务端向商户发起通知(支付通知、退款通知),进行签名; 商户使用平台公钥验签 |
5. 应用订阅接入介绍
PC端订阅SDK提供联想账号登录,以及支付功能;支付包括使用联想账号支付、使用非联想账号支付以及设备账号支付,若接入联想账号则使用联想账号支付,否则使用非联想账号支付。
5.1. 支付接入介绍
1. 订阅服务提供的支付能力包括支付、7天无理由退款。
2. 支付的接入,需在应用集成订阅SDK后,调用订阅SDK的支付接口,打开联想通用收银台。
3. 7天无理由退款服务包括应用内无理由退款和联想软件商店内无理由退款两个入口。
a) 应用内无理由退款
i. 退款的接入以CP直接调用服务端退款接口实现,待退款审核通过后通知CP
ii. 满足退款条件的订单,经审核后(周期1-2个工作日),原路退全款至用户账户
iii. 不满足退款条件的订单,接口返回失败,不执行退款流程
b) 联想软件商店内无理由退款
i. 该退款入口不需要CP开发,但需要CP提供退款通知地址,用于用户在联想软件商店内退款成功后,通过该地址进行退款成功的通知,再由CP进行权益变更的操作
5.2. 支付流程介绍
1. CP创建业务订单,确定支付产品编码,调用创单接口
2. 产品编码选择COMMON_CASHIER、COMMON_DK_CASHIER,调起联想通用收银台;
3. 用户在联想通用收银台中扫码支付,支付成功后PC端订阅SDK会同步返回支付结果给应用客户端,且订阅服务也会异步通知CP服务端支付结果。注意:用户权益的变更,需以服务端的通知结果为准。
5.3. 周期扣款接入介绍
1. 订阅服务提供的周期扣款能力是用户在支付时,与支付宝、微信签约代扣协议,按照协议完成后续扣款;
2. 应用内有周期扣款需求的接入方需在支付接口里传入代扣相关字段;
3. 为了保持良好的用户体验,联想侧要求在支付宝/微信的签约页面显示完整的用户信息,若未接入联想账号登录功能的应用,需传入用户id、用户账号等信息。
4. 接入周期扣款后,用户初次签约并支付时,由cp向订阅服务发起支付请求,后续扣款由订阅服务发起,收到三方支付渠道(支付宝、微信)的扣款通知后,订阅服务将扣款成功通知发送给cp服务端;
5. 支持cp主动查询协议信息,可避免因服务不稳定导致的未收到解约通知的问题。
5.4. 退款功能说明
1. 退款功能说明
- 订阅服务支持7天无理由退款能力,退款条件:
(1) 每个应用每个用户只有一次无理由退款权力
(2) 自支付成功时起7日内
- 订阅服务提供cp应用内退款的能力,以调用服务端接口(详见文档章节4.5)实现
(1) 满足退款条件的订单,经我方人工审核后(周期1-2天),原路退全款至用户账户
(2) 不满足退款条件的订单,接口返回失败,不执行退款流程
2. 退款时序
6. PC端订阅SDK接入
6.1. 发行包介绍
发行包内容 | 解释说明 |
订阅SDK动态库 | 联运SDK的主要功能载体,包含LYSDK2.dll,LsfSdk.dll和WebView2Loader.dll,接入方应用发布时,需要带上这三个DLL动态库,并确保这三个文件放在一个目录下 |
订阅SDK测试程序 | 使用联运SDK动态库的测试程序(测试程序.exe),通过运行这个测试程序,可以体验SDK各个接口的功能 |
订阅SDK头文件和静态库 | 给开发使用的,接入方应用发布时,并不需要带上这部分 |
订阅SDK2.0包含LYRelease32和LYRelease64两个目录,分别对应32位版本和64位版本,32位的接入方应用使用LYRelease32目录中的接口文件,64位的接入方应用使用LYRelease64目录中的接口文件,具体如下图所示:
以32位版本为例,具体如下图所示:
LYSDK2.dll是订阅SDK的核心动态库,通过其文件属性中的产品版本来查看订阅SDK的版本号信息,具体如下图所示:
6.2. 接口约定说明
参数约定 | |
char * | 字符串必须使用UTF8编码,便于跨语言接入 |
[in] | 表示这个参数是接入方传入给订阅SDK的参数 |
[out] | 表示这个参数是订阅SDK返回给接入方的参数 |
返回值约定 | |
0 | 成功 |
大于0 | 失败 (不同的整数编码不同的错误) |
6.3. 登录功能相关接口
6.3.1. 数据结构定义
"loginInfo": {
"version":"1.0" , //Json结构的版本号信息,便于后续扩展
"loginstatus":true/false,//bool类型,表示LID账号是否已经登录
"token":"abcd"/"null" //token信息,如果token无效,将返回“null”字符串
}
loginInfo 是Json(不是XML)结构字符串,该结构保存LID账号的状态信息。
注意:
LID登录状态会保存在订阅SDK本地,当接入方登录后,程序关闭,LID依然会保持登录状态,但LID是登录状态,不一定可以拿到token。(比如没有联网,本地缓存的token又失效)。建议在需要拿token进行业务交互,检验到token已失效时,则弹出登录窗口让用户重新登录。
6.3.2. 接口定义
1. 联想LID账号登录信息回调函数指针定义
由接入方实现,通过LYSDK_LidInit接口传入订阅SDK,当发生LID账号登录信息时,会通过该回调函数通知接入方。
typedef void(__stdcall *LYSDK_LOGIN_CALLBACK)(char* loginInfo);
loginInfo [out]:Json字符串,返回登录相关信息
2. 联想LID账号登出信息回调函数指针定义
由接入方实现,通过LYSDK_LidInit接口传入订阅SDK,当发生LID账号登出信息时,会通过该回调函数通知接入方。
typedef void(__stdcall *LYSDK_LOGOUT_CALLBACK)(int errorCode);
errorCode [out]:登出结果
6.3.3. 订阅SDK初始化接口
说明:基于单点登录提高用户体验的考虑,必须在接入方程序启动时调用,联想LID登录登出状态变化,通过异步回调的方式实现。
调用该方法:
Int LYSDK_LidInit(char* appInfo, LYSDK_LOGIN_CALLBACK login_listener, LYSDK_LOGOUT_CALLBACK logout_listener);
appInfo[in]:Json字符串,接入方传入的应用信息,包括mchId,appid,key等
login_listener [in]: 接入方实现的登录通知回调函数
logout_listener [in]:接入方实现的登出通知回调函数
C++示例代码:
void __stdcall OnLoginResultEX( char* loginInfo)
{
CString wstrLoginInfo = CA2W(loginInfo, CP_UTF8);
MessageBox(0, wstrLoginInfo,L"LYSDK_LOGIN_CALLBACK loginInfo",0);
}
void __stdcall OnLogoutResultEX(int errorCode)
{
if(errorCode==0)
MessageBox(0, L"Logout Succeed", L"LYSDK_LOGOUT_CALLBACK", 0);
else
MessageBox(0, L"Logout Failed", L"LYSDK_LOGOUT_CALLBACK", 0);
}
CString strParam = L"{\"appid\":\"471561429588736\",\"mchId\":\"471561429588736\",\"key\":\"yJiUHopRvJEaeyb49AbO7kCTOrQ43kWsECm/zue1zM5NUxZb/1K5N18/cLZR8ZFkzqvRYAkflOxft5eABSsu6iyNOoiSmfPhRKGaeu+dMHsEeZWYhUGipc9wFMzne57tZ4Oj+xpaRYaLvzAgMBAAECggEAXPmnmmoirP4NNr011YeT9DbiZ++W58qpyfdTl4YNaoSD9Yg93G08efEtJu5z2hvmbBm6YVy14QTqoV0qPU6rGcJOJxjsGdnNZM7OjSlIpEs2/x7RhduMSvMf+hwWUj5fXDgL5b1M5bmtMGJw/AGkMQO5nPy4MYfh8IMoXKrswYvEQEcAK2KW69ajIsf71cwjjWqptjjYQXDY+xeynSbnUTrA14XhevMWPzYuvoCD28PRX/Jwj+RoDAFr3llP79bGjg70g+E9eCdA6knBHgP2haQ+nEKUkSXJL1Kzzl4qb7Ov1QpIwa22g/qMiSCOzpL69/oqq7nPx5DFCSdg3BVzOQKBgQD5WIUo0BtnBoGpOCxb+6SxdHh6e3QUAmUwRm/Y0xBBdklq+SayLi9ycYU8Kg9viaBW2l/Y/Lrt6YuUIPnJQBJHn0k6JmFD3jc6DTTg1RaNv1HjmUiIyIkfXaNKu26z9iEh7Yvr4gVlhfKu8LHftv7fe4dbCjE1QrUvolz3jE+kbwKBgQDaMnDFijKwORpUjLympmAQNR7cRImqyaa2aGMxwZ+/uBKD8KPP0vsBEsYh5gRCTMdsJmnREBhCsN6cKAIf/SixcRPqbEauH2IW5zkrzKtqLU6H8BOXwixlpWAUQdcjIXDuwLR80DtVNSJudXqGY+pQdBOvUUuvCVpFKRNjJdAKvQKBgHp/n6PGYcACtU6yVzC1d1rrEolx/zwZVScbY7WNM85FU5pnXzhockzyMne1XjH41jn1PON4fkmF1FnovW6+uHxRaANWebCDCnTNqi3O4i6vUIbVdookoyEyupdrb96fy/VEI9q2BtqyrOB/RZPX7m1S3dO/NR+qbyNAzBWd7D6nAoGBAKdk5wtFeJmlVUPkLJ6VKUGF30UQ96Sky1dJQkBb0RieOOYr6SB5NcOfCY3cDwxmJuAboDaZaGNRmZanQuoTp/JpU+QWaSsQPIphnYSyohb05zh2wDE+8ByTAODJmikDcMZZ5J2UitBV0TJ4wiTz5kEOrENl1PBV9oD0gEEiMzbpAoGAcqvZpSbU+snP5Pguq3MuA2zM+mFIH5MrUaaGzTTE4skhAdpdFgwkyhi7jz5l3qJRS2mLaINQ+XXBu+c69DYPZTX4/QAT+E1+5hVDAj8EQLhCC9FGrHwImG0oAUoRMQMeLCDqQ/THpCJXFK8d6kXUNJ/TvicMn03O5beDDwIeE0k=\"}";
CStringA strP = CT2A(strParam, CP_UTF8);
int nRet = LYSDK_LidInit(strP.GetBuffer(0) ,&OnLoginResultEX, &OnLogoutResultEX);
if (nRet!=0)
{
MessageBox(0, L"LYSDK_LidInit Error",L"Error",0);
}
6.3.4. 订阅SDK退出接口
说明:一般在接入方程序结束前调用。
调用该方法:
int LYSDK_LidClose();
C++示例代码:
int nRet = LYSDK_LidClose();
if (nRet!=0)
{
MessageBox(0, L"LYSDK_LidClose Error",L"Error",0);
}
6.3.5. 获取LID账号相关信息接口
说明:一般在LYSDK_LidInit后直接调用,该接口除了返回LID的登录状态外,还会实现单点登录和自动登录。
调用该方法:
int LYSDK_LidGetLoginStatus(char* loginInfo,int bufLen);
loginInfo [out]:接收loginInfo信息指针,该字符串需接入方分配空间,然后传入
bufLen [in]:接收loginInfo字符串长度,该长度需要大于等于1024字节
C++示例代码:
char szLogInfo[1024] = { 0 };
int nRet = LYSDK_LidGetLoginStatus(szLogInfo, sizeof(szLogInfo)/sizeof(char));
if (nRet!=0)
{
MessageBox(0, L"LYSDK_LidGetLoginStatus Error",L"Error",0);
}
6.3.6. LID账户登录接口
说明:在LID未登录状态时调用,该接口是异步实现,弹出登录框后会立即返回接入方,实际的登录结果会通过LYSDK_LidInit中注册的login_listener回调通知。
调用该方法:
int LYSDK_LidLogin(HWND hwndParent=NULL);
hwndParent[in]:接入方传入的接入应用窗口句柄,登录弹窗会把该句柄作为父窗口,以便于显示在这个窗口上面。
C++示例代码:
int nRet = LYSDK_LidLogin(m_hWnd);
if (nRet!=0)
{
MessageBox(0, L"LYSDK_LidLogin Error",L"Error",0);
}
6.3.7. LID账户登出接口
说明:在LID登录状态下,如果需要退出LID登录时调用,该接口是异步实现,调用后立即返回接入方,实际的登出结果会通过LYSDK_LidInit中注册的logout_listener回调通知。
调用该方法:
int LYSDK_LidLogout();
C++示例代码:
int nRet = LYSDK_LidLogout();
if (nRet!=0)
{
MessageBox(0, L"LYSDK_LidLogout Error",L"Error",0);
}
6.4. 支付功能相关接口
说明:客户端通过回调函数接受支付结果,及订单号。
/*
** 支付结果回调
**
* nRetCode[out] PAYCOMPLETED: 7110// 完成支付 PAYCANCEL:7111 // 未支付
* orderNum[out] 订单号
*/
typedef void(__stdcall *LYSDK_PAYSTATUS_CALLBACK)(int nRetCode, char* orderNum);
6.4.1. 联想账号支付接口
说明:接入LID后支付时调用;
调用方法:
extern "C" int __stdcall LYSDK_PayByToken(HWND hwndParent, char* prikey, char* in_param, char* token, char *outbufer, int bufLen, LYSDK_PAYSTATUS_CALLBACK payStatus_callback);
/*
函数功能:lenovoid账号登录 支付
** hwndParent[in]: 父窗口句柄
** prikey[in]: 签名私钥
** in_param [in]: Json字符串,接入方传入的支付参数,参考开发文档( 商户订单号、支付金额等)
** token[in]: lid用户token
** outbufer[out]: 产品编码为COMMON_CASHIER、COMMON_DK_CASHIER时,没有数据返回(json)
** buflen[in]:接收outbufer的长度
** payStatus_callback[in] 回调通知支付结果, 可以为NULL ,为NULL时则不回调
*/
参数说明:
参数 | 类型 | 描述 | 必填 |
普通支付 | |||
appId | Long | 平台分配商户号下创建的appId | Y |
mchId | Long | 平台分配的商户号 | Y |
mchNo | String (128) | 商户订单号参考5.1 说明 | Y |
attach | String (128) | 商品附加信息 | N |
goodsDes | String (128) | 商品描述 | N |
goodsName | String (128) | 商品名称 | Y |
goodsCode | String (128) | 商品编码 | Y |
payAmount | Long | 支付金额,单位为分,大于等于1 | Y |
payNotify | String (256) | 支付通知地址 | Y |
productCode | String(32) | 普通支付:COMMON_CASHIER代扣支付:COMMON_DK_CASHIER | Y |
周期扣费 | |||
deductionFee | long | 代扣金额;未传则以支付金额作为后续扣款金额 | N |
deductionPeriod | int | 代扣周期,单位为自然月 | Y |
firstDeductionDate | String | 首次扣款日期,日期格式yyyy-MM-dd,如2023-05-26 | N |
contractNotify | String (255) | 协议通知地址,签约和解约通知地址 | Y |
productId | String (32) | 产品id,由联想侧提供 | Y |
6.4.2. 非联想账号支付接口
说明:未接入LID,使用三方账号支付时调用;
调用方法:
extern "C" int __stdcall LYSDK_Pay(HWND hwndParent, char* prikey, char* in_param, char *outbufer, int bufLen, LYSDK_PAYSTATUS_CALLBACK payStatus_callback);
/*
函数功能:无账号 支付
** hwndParent[in]: 父窗口句柄
** prikey[in]: 签名私钥
** in_param [in]: Json字符串,接入方传入的支付参数,参考开发文档( 商户订单号、支付金额等)
** outbufer[out]: 产品编码为COMMON_CASHIER、COMMON_DK_CASHIER时,没有数据返回(json)
** buflen[in]:接收outbufer的长度
** payStatus_callback[in] 回调通知支付结果, 可以为NULL ,为NULL时则不回调
*/
参数说明:
参数 | 类型 | 描述 | 必填 |
普通支付 | |||
appId | Long | 平台分配商户号下创建的appId | Y |
mchId | Long | 平台分配的商户号 | Y |
mchNo | String (128) | 商户订单号参考5.1 说明 | Y |
attach | String (128) | 商品附加信息 | N |
goodsDes | String (128) | 商品描述 | N |
goodsName | String (128) | 商品名称 | Y |
goodsCode | String (128) | 商品编码 | Y |
payAmount | Long | 支付金额,单位为分 | Y |
payNotify | String (256) | 支付通知地址 | Y |
productCode | String(32) | 普通支付:COMMON_CASHIER代扣支付:COMMON_DK_CASHIER | Y |
周期扣费 | |||
deductionFee | long | 代扣金额 | N |
deductionPeriod | int | 代扣周期,单位为月 | Y |
firstDeductionDate | String | 首次扣款日期,日期格式yyyy-MM-dd,如 | N |
2023-05-26 | |||
contractNotify | String (255) | 协议通知地址,签约和解约通知地址 | Y |
userId | String (32) | 用户id | Y |
userAccount | String (64) | 用户账号 | Y |
productId | String (32) | 产品id,由联想侧提供 | Y |
6.4.3. 设备账号支付接口
订阅前置条件:应用的用户权益仅在用户的单台设备生效。
若需具体了解此订阅能力,可联系联想商务提供支持。
说明:设备账号订阅,支付时调用。
调用方法:
extern "C" int __stdcall LYSDK_PayByDeviceID(HWND hwndParent, char* prikey,char* in_param, char *outbufer, int bufLen, LYSDK_PAYSTATUS_CALLBACK payStatus_callback);
/*
函数功能:设备账号 支付
** hwndParent[in]: 父窗口句柄
** prikey[in]: 签名私钥
** in_param [in]: Json字符串,接入方传入的支付参数,参考开发文档(设备id 商户订单号、支付金额等)
** outbufer[out]: 产品编码为COMMON_CASHIER、COMMON_DK_CASHIER时,没有数据返回(json)
** buflen[in]:接收outbufer的长度
** payStatus_callback[in] 回调通知支付结果, 可以为NULL ,为NULL时则不回调
*/
参数说明:
参数 | 类型 | 描述 | 必填 |
appId | Long | 平台分配商户号下创建的appId | Y |
mchId | Long | 平台分配的商户号 | Y |
mchNo | String (128) | 商户订单号参考5.1 说明 | Y |
attach | String (128) | 商品附加信息 | N |
goosDes | String(128) | 商品描述 | N |
goodsName | String(128) | 商品名称 | Y |
goodsCode | String(128) | 商品编码 | Y |
deviceId | |||
payAmount | Long | 支付金额,单位为分 | Y |
productCode | String(32) | 普通支付:COMMON_CASHIER代扣支付:COMMON_DK_CASHIER | Y |
6.4.4. 代码示例
std::string strPriKey=” ****”;
std::string strin_param=” {
\"appId\":11217136061*****,
\"mchId\":11217125827*****,
\"mchNo\":\"e33bdf96-9a97-12\",
\"payAmount\":100,
\"goodsName\":\"测试\",
\"goodsCode\":\"goodCoe001\",
\"productCode\": \"COMMON_CASHIER\" ,
\"goodsDes\":\"goodDes支付\",
\"deviceId\":\"***** \",
\"payNotify\":\"https://www.baidu.com/cloud-intermodal-coretify/pay\"
}”;
Std::string strToken = “ZAgAAAAAAAGE9MTAxNjA3MTU0NzkmYj0yJmM9MSZkPTE3ODc3MSZlPTY1MDE4N0I5NkJCNDRBRjBEMjBDNDZGMzdCRDgyRkJGMSZoPTE2ODc3NzI0ODUzMjUmaT00MzIwMCZvPWFiMDMxMmZlMmIxNzZlOTA2NGZhZWJlZGViMDBkNjMzJnA9d2F1dGgmcT0wJnVzZXJuYW1lPWxpaHQxMCU0MGxlbm92by5jb20mYW1yPXVua25vd24maWw9Y26Eh0VCDeBtKuetPoew2LIx”;
char outbufer[2048]; int bufLen = 2048;
LYSDK_Pay(NULL,strPriKey.c_str(), strin_param.c_str(),outbufer, bufLen,NULL);
LYSDK_PayByToken (NULL,strPriKey.c_str(), strin_param.c_str(),strToken.c_str(),outbufer, bufLen,NULL);
LYSDK_PayByDeviceID(NULL,strPriKey.c_str(), strin_param.c_str(),outbufer, bufLen,NULL);
6.4.5. Demo运行说明
(1) 输入支付接口签名私钥(找联想运营获取)
(2) 在LYRelease32或LYRelease64文件夹内,新增一个payContent.json文件,内容如下:
{
"appId":***************,
"mchId":***************,
"mchNo":"242322251101",
"payAmount":100,
"goodsName":"测试",
"goodsCode":"test",
"productCode": "COMMON_CASHIER" ,
"goodsDes":"支付",
"deviceId":"***************",
"payNotify":"https://www.lenovo.com"
}
7. 服务端接口
7.1. 接口说明
API 采用REST风格设计。所有接口请求地址都是可预期的以及面向资源的。使用规范的HTTP响应代码来表示请求结果的正确或错误信息。所有的API请求都会以规范友好的JSON对象格式返回(包括错误信息)。
- 请求参数格式说明:
请求参数分为2部分,Body和head。Head参数不参与签名,为鉴权所用。Body参数分为基础参数和业务参数。所有基础参数都为必填参数,业务参数根据不同的场景,有所不同。
- http状态码说明:
HTTP状态码 | 描述 |
200 | OK |
401 | 签名或者token无效 |
500 | 错误信息 |
- 接口统一响应数据格式说明
{
"code": 10000,
"data": "",
"message": "success"
}
变量名 | 必填 | 类型 | 描述 |
code | Y | String | 返回码,发生错误时code不等于10000 |
data | Y | Object | 返回数据,发生错误时返回null |
message | Y | String | 返回码描述,发生错误时返回错误信息 |
7.2. 支付通知
- 当用户支付完成后,订阅服务端将数据post到支付通知地址。商户返回SUCCESS则表示该订单支付通知,商户成功接收。商户返回其它任何字符都代表接收失败。
- 订阅服务端会再次发送通知。通知触达10次,到达通知次数后,不会触发通知。
Post数据 | |||||
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
商户ID | mchId | Y | Long | 78993434342 | |
应用ID | appId | Y | Long | 343453453445 | |
签名 | sign | Y | String | 参考签名算法 | |
签名方式 | signType | Y | String | RSA2 | 固定值RSA2 |
版本 | version | Y | String | 1.0 | 固定值1.0 |
编码 | charSet | Y | String | utf-8 | 固定值utf-8 |
随机字符 | nonce | Y | String | dsafsadfasdfasa | 随机字符串 |
商户订单号 | mchNo | Y | String | ||
交易订单号 | tradeNo | Y | Long | 9860494571601920 | 交易订单号 |
银行流水号 | serialsNo | Y | String | 2019090239230923 | 第三方支付渠道的交易流水号 |
支付金额 | payFee | Y | Int | 1 | 支付金额单位(分) |
支付状态 | payStatus | Y | String | 支付状态 1 已支付 | 支付状态 1 已支付 |
支付时间 | payTime | Y | String | 2022-03-23 19:17:56 | yyyy-MM-dd HH:mm:ss |
附加数据 | attach | N | String | Dfasas | 商户私有数据 |
- 商户需返回参数
SUCCESS
通知示例代码:
{
"mchNo": "*********1111",
"tradeNo": 969549918857856,
"serialsNo": "***********2222",
"payFee": 3000,
"payTime": "2022-10-19 17:23:12",
"payStatus": "1",
"appId": 12312312,
"mchId": 123123123,
"sign": "T2IuyxblOnKstHf7EuJZWGBroSUzwN4n6l6yZ1zahsvGfBDn2AxAhifpPUblbMuq2XHsh9Mvg==",
"signType": "RSA2",
"version": "1.0",
"charSet": "UTF-8",
"nonce": "m1wo53f6iy"
}
7.3. 查询支付结果接口
- 接口地址:
https://cloud-rest.lenovomm.com/cloud-intermodal-core/api/v1/pay/query/trade
- 请求方式:POST
- 请求头参数说明:Content-Type: application/json
- 请求Body参数说明:
1.基础参数
基础参数 | |||||
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
商户ID | mchId | Y | Long | 96291345 | 分配的商户号 |
应用ID | appId | Y | Long | 11962913 | 商户号下创建的APPID |
随机字符串 | nonce | Y | String | 5K8264ILTKCH16C | 随机字符串,不长于32位 |
时间戳 | timestamp | Y | String | 1648878162182 | 时间戳(从1970-01-01T00:00:00Z开始的毫秒数) |
版本 | version | Y | String | 1.0 | 版本号 固定值1.0 |
签名 | sign | Y | String | 参考5.4签名算法 | |
签名方式 | signType | Y | String | RSA2 | 签名方式RSA2 |
2. 业务参数说明
业务参数 | |||||
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
交易订单号 | tradeNo | Y | Long | 524498630711936 |
- 返回参数说明:
字段名 | 变量名 | 必填 | 类型 | 示例值 |
返回码 | code | Y | String | 10000 |
返回消息 | msg | Y | String | SUCCESS |
返回数据体 | data | N | String |
示例:
- 请求参数:
{
"mchId": 233799539766016,
"appId": 233799539766016,
"sign": "ngAcXTBZnuuI944ae8pyMY60CpiRILjTm2uX***SEwHG19+aWotz",
"signType": "RSA2",
"timestamp": "1666593772589",
"nonce": "w190bfrrog1666593772585",
"version": "1.0",
"tradeNo": 977319841453440,
}
- 返回参数:
http状态200
{
"code": 10000,
"message": "Success",
"data": {
"tradeNo": 877319841453440,
"serialsNo": "4200001493202206141879749159",
"payStatus": 1,
"payTime": "2022-06-14 11:17:48",
"payAmount": 1
}
}
data 数据说明:
变量名 | 必填 | 类型 | 描述 |
tradeNo | Y | long | 交易订单号 |
serialsNo | N | String | 交易流水号 |
payStatus | Y | Byte | 支付状态:待支付:0,已支付:1 |
payTime | N | String | 支付时间 yyyy-MM-dd HH:mm:ss |
payAmount | Y | BigDecimal | 支付金额单位分 |
错误返回数据:
http状态码:非200
{"code":401,"message":"unauthorizedException","data":null}
{"code":500,"message":"error","data":null}
7.4. 申请退款接口
- 接口地址:
https://cloud-rest.lenovomm.com/cloud-intermodal-core/api/v1/pay/refund
- 请求方式:POST
- 请求头参数说明:Content-Type: application/json
- 请求Body参数说明:
1.基础参数
基础参数 | |||||
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
商户ID | mchId | Y | Long | 96291345 | 分配的商户号 |
应用ID | appId | Y | Long | 11962913 | 分配的APPID |
随机字符串 | Nonce(32) | Y | String | 5K8264ILTKCH16C | 随机字符串,不长于32位 |
时间戳 | timestamp | Y | String | 1648878162182 | 时间戳(从1970-01-01T00:00:00Z开始的毫秒数) |
版本 | version | Y | String | 1.0 | 版本号 固定值1.0 |
签名 | sign | Y | String | 参考签名算法 | |
签名方式 | signType | Y | String | RSA2 | 签名方式RSA2 |
2. 业务参数说明
业务参数 | |||||
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
交易号 | tradeNo | Y | Long | Fjkj2345 | 交易订单号 |
退款金额 | refundAmount | Y | Int | 100 | 参考5.2金额说明 |
退款通知地址 | refundNotify | Y | String(256) | http://www.***.com/notify | 异步接收退款成功通知,通知url必须为外网可访问的url。 |
- 返回参数说明:
字段名 | 变量名 | 必填 | 类型 | 示例值 |
返回码 | code | Y | String | 10000 |
返回消息 | msg | Y | String | SUCCESS |
返回数据体 | data | N | String |
示例:
请求参数:
{
"mchId":233799539766016,
"appId":233799539766016,
"sign":"PZP0ZYI0BPXAn2YBD15hG0L9qNNG3Q5uw048bzgKCMv3F8asUbSD7S0pPXxmoKWDigaUNJsHUVFE6hVFkVWDy+SOparIauqa8vMCHTtbhDZe7V/40GcyNd7q4IwnvsmqBOhCV9jZs5J8a9A9K+FqxVlWW2iEgU6UTjrvWEc62GhnzMdsw1AM1yki/Zr0PjmR7kwK1u34AmaGKmTBmReTLl/27usi/gcXw99FcQYIuyIIpDZbpdApAYwLaZvf5qLuPJDg1I7YJmmUiOxdMpKnbmiAUph6LK0nptiCKii+WqX1F+2hQsaLioWGZbXwhDmPEDb4QafJJ0UvfWqWOuCQnA==",
"signType":"RSA2",
"timestamp":"1648881113851",
"nonce":"nonceStr2bb1145d-bbc3-4c",
"version":"1.0",
"tradeNo":821614685996160,
"refundAmount":1,
"refundNotify":"https://www.***.com/api/v1/notify/refund"
}
返回参数:
http状态200
{"code":10000,"message":"success","data":null}
错误返回数据:
http状态码:非200
{"code":401,"message":"unauthorizedException","data":null}
{"code":500,"message":"{ refundAmount.Null.message}","data":null}
7.5. 退款通知
- 当退款单完成退款后,订阅服务端将数据post到退款通知地址。商户返回SUCCESS则表示该订单退款通知,商户成功接收。商户返回其它任何字符都代表接收失败。
- 订阅服务端会再次发送通知。通知触达10次,到达通知次数后,不会触发通知。
Post数据 | |||||
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
商户ID | mchId | Y | Long | 78993434342 | |
应用ID | appId | Y | Long | 343453453445 | |
签名 | sign | Y | String | 参考签名算法 | |
签名方式 | signType | Y | String | RSA2 | 固定值RSA2 |
版本 | version | Y | String | 1.0 | 固定值1.0 |
编码 | charSet | Y | String | utf-8 | 固定值utf-8 |
随机字符 | nonce | Y | String | dsafsadfasdfasa | 随机字符串 |
商户订单号 | mchNo | Y | String | ||
交易号 | tradeNo | Y | Long | 9860494571601920 | 交易号 |
退款金额 | refundFee | Y | BigDecimal | 1 | 金额单位(分) |
退款状态 | refundStatus | Y | String | 退款状态 2 已完成 | 退款状态 2 已完成 |
退款时间 | refundTime | Y | String | 2022-03-23 19:17:56 | yyyy-MM-dd HH:mm:ss |
返回参数说明:
SUCCESS
通知示例代码:
{
"mchNo": "123123123",
"tradeNo": 964881759168896,
"refundFee": 12,
"refundTime": "2022-10-17 17:20:37",
"refundStatus": "2",
"appId": 132312312,
"mchId": 12312312,
"sign": "Fh3/fxxPfghQElZyV7dTPvvLHndoAs8PZv0WSEty2hgG6SY04b/B5uNWTd0+pKJGCpauiY0nrB7XznoIIA9JDjrxIEK+Qu+4s4fG9pLWBDiLRLNnS/QegVded+2LbSTZMdIFeCRCEmxTcC00dcRXrJLRAslltpGmvQMTz + P5CopmQPAqr5k + /XELCrMxuCxavXnxfCn0oCjSHLnhQybV7kvwLrFwPCWthnvUflcG+b5yIMGdaRqjZC32v5jXDflpcvcQLWuSafSJn4fVoLXuYsl7ILzMkNfN1IXRRG5KHIsVo2GnibuzcbJIgSlVKpgLbfb56M0w / o4rKio7Mch / Q == ",
"signType ": "RSA2 ",
"version ": "1.0 ",
"charSet ": "UTF - 8 ",
"nonce ": "xnca609z7z "
}
7.6. 根据token获取lenovoID和用户账号
- 接口地址:
https://cloud-rest.lenovomm.com/cloud-intermodal-core/api/v1/oauth/token
- 请求方式:POST
- 请求头参数说明:
token: "ZAgAAAAAAAGE ",请求头里面必须添加token参数。从SDK提供方法获取token
realm: pcapp.lenovomm.com,固定值
- 请求Body参数说明:
请求参数
基础参数 | |||||
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
商户ID | mchId | Y | Long | 96291345 | 分配的商户号 |
应用ID | appId | Y | Long | 11962913 | 分配的APPID |
随机字符串 | nonce | Y | String | 5K8264ILTKCH16C | 随机字符串,不长于32位 |
时间戳 | timestamp | Y | String | 1648878162182 | 时间戳(从1970-01-01T00:00:00Z开始的毫秒数) |
版本 | version | Y | String | 1.0 | 版本号(1.0、2.0) 2.0 返回用户信息用户账号信息,具体看响应结果 |
签名 | sign | Y | String | 参考签名算法 | |
签名方式 | signType | Y | String | RSA2 | 签名方式RSA2 |
- 返回参数说明:
字段名 | 变量名 | 必填 | 类型 | 示例值 |
返回码 | code | Y | String | 10000 |
返回消息 | msg | Y | String | SUCCESS |
返回数据体 | data | N | String |
示例:
请求参数:
{ "mchId":233799539766016,
"appId":233799539766016, "sign":"viHirVTUoz5M3FsnBNq4BtvHxam+GqkNyFrsbYbY4gXt/cy9oa5AmFrq21QXl2+05ao63zkuKyNE/0/UtSsKb0qeLV5rv8YZWi87EO98lbLAFz6h0R9HP01ZdOKCgUmbB1XxA3mdNBCi9/FSCsFpFQ5QxoFhiXPbXTUIEDqEajd92LaqHZZZdsaxCUQDvbRmik3RtG5jJWfMDaTGx3+NffKWyWFR+lMkI0ZcsbcAlS8zDet72fNJpiOZIN2Yh8+Rtn7rRJ2Ok0zu3349P3QvQ/GYxF8R8VhVJ08sBBSle7pa5e0UhRq9MdtO7vKWCmAiyy8KNHxswuj7NThOOPvT9Q==",
"signType":"RSA2",
"timestamp":"1649215075332",
"nonce":"nonceStr82751c47-ab56-4f",
"version":"1.0"
}
返回参数:
http状态200
version为1.0时返回结果:
{"code":10000,"message":"Success","data":"10086910024"}
version为2.0时返回结果:
{ "code": 10000, "message": "Success",
"data": {
"lenovoId": "10086910024",
"account": "s*******@lenovo.com"
}
}
错误返回数据:
http状态码:非200
{"code":401,"message":"unauthorizedException","data":null}
{"code":500,"message":"{ refundAmount.Null.message}","data":null}
7.7. 周期扣款—协议查询接口
- 接口地址:
https://cloud-rest.lenovomm.com/cloud-intermodal-core/api/v1/contract/query
- 请求方式:POST
- 请求头参数说明:Content-Type: application/json
- 请求Body参数说明:
1.基础参数
基础参数 | |||||
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
商户ID | mchId | Y | Long | 96291345 | 分配的商户号 |
应用ID | appId | Y | Long | 11962913 | 商户号下创建的APPID |
随机字符串 | nonce | Y | String | 5K8264ILTKCH16C | 随机字符串,不长于32位 |
时间戳 | timestamp | Y | String | 1648878162182 | 时间戳(从1970-01-01T00:00:00Z开始的毫秒数) |
版本 | version | Y | String | 1.0 | 版本号 固定值1.0 |
签名 | sign | Y | String | 参考5.4签名算法 | |
签名方式 | signType | Y | String | RSA2 | 签名方式RSA2 |
2. 业务参数说明
业务参数 | |||||
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
协议单号 | contractId | N | Long | 524498630711936 | 不能同时为空 |
商户订单号 | mchNo | N | String | 12312312 |
返回参数说明:
字段名 | 变量名 | 必填 | 类型 | 示例值 |
返回码 | code | Y | String | 10000 |
返回消息 | msg | Y | String | SUCCESS |
返回数据体 | data | N | String |
示例:
请求参数:
{
"mchId": 1121712582*****,
"appId": 112171361******,
"sign": "QkX5k62T5KUwerI7TJZrGT ==",
"signType": "RSA2",
"timestamp": "1697106066664",
"nonce": "sp5xnnh5sa1697106066660",
"version": "2.0",
"contractId": 105924711500*****,
"mchNo": "6f5cce50-fca3-4f"
}
返回参数:
http状态200
{
"code": 10000,
"message": "Success",
"data": {
" mchNo": 8773198414*****,
" contractId": "42000014932022061*****",
" status ": 1,
" time ": "2022-06-14 11:17:48"
}
}
data 数据说明:
变量名 | 必填 | 类型 | 描述 |
contractId | Y | long | 协议号 |
mchNo | Y | String | 商户交易号 |
status | Y | Byte | 状态:未签约:0,已签约:1 已解约2 |
time | N | String | 变更时间 yyyy-MM-dd HH:mm:ss |
错误返回数据:
http状态码:非200
{"code":401,"message":"unauthorizedException","data":null}
{"code":500,"message":"error","data":null}
7.8. 每日订单查询接口
- 接口地址:
https://cloud-rest.lenovomm.com/cloud-intermodal-core/api/v1/app/query/trade
- 请求方式:POST
- 请求头参数说明:Content-Type: application/json
- 请求Body参数说明:
1.基础参数
基础参数 | |||||
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
商户ID | mchId | Y | Long | 96291345 | 分配的商户号 |
应用ID | appId | Y | Long | 11962913 | 商户号下创建的APPID |
随机字符串 | nonce | Y | String | 5K8264ILTKCH16C | 随机字符串,不长于32位 |
时间戳 | timestamp | Y | String | 1648878162182 | 时间戳(从1970-01-01T00:00:00Z开始的毫秒数) |
版本 | version | Y | String | 1.0 | 版本号 固定值1.0 |
签名 | sign | Y | String | 参考5.4签名算法 | |
签名方式 | signType | Y | String | RSA2 | 签名方式RSA2 |
2. 业务参数说明
业务参数 | |||||
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
日期 | date | Y | String | 2023-08-01 | 可传入最近一个月内的日期进行查询 |
- 返回参数说明:
字段名 | 变量名 | 必填 | 类型 | 示例值 |
返回码 | code | Y | String | 10000 |
返回消息 | msg | Y | String | SUCCESS |
返回数据体 | data | N | String |
示例:
请求参数:
{
"mchId": 11217125827*****,
"appId": 11217136061*****,
"sign": "qseokxdhDkH+jHz0wLluVQyvCvxGSw==",
"signType": "RSA2",
"timestamp": "1697106525038",
"nonce": "0rcscwivez1697106525033",
"version": "2.0",
"date": "2023-10-07"
}
- 返回参数:
http状态200
{
"code": 10000,
"message": "Success",
"data": {
"date": "2023-10-07",
"fileUrl": "https://pr1-greentea-test.lenovo.com.cn/default/ccd41.xlsx"
}
}
data 数据说明:
变量名 | 必填 | 类型 | 描述 |
date | Y | String | 交易日期 yyyy-MM-dd |
fileUrl | Y | String | 文件地址 |
错误返回数据:
http状态码:非200
{"code":401,"message":"unauthorizedException","data":null}
{"code":500,"message":"error","data":null}
7.9. 周期扣款—协议通知
- 用户签约、解约后,订阅服务端将数据post到协议通知地址(3.5 协议通知地址)。商户返回SUCCESS则表示该协议通知,商户成功接收。商户返回其它任何字符都代表接收失败。
- 订阅服务端会再次发送通知。通知触达10次,到达通知次数后,不会触发通知。
Post数据 | |||||
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
商户ID | mchId | Y | Long | 78993434342 | |
应用ID | appId | Y | Long | 343453453445 | |
签名 | sign | Y | String | 参考签名算法 | |
签名方式 | signType | Y | String | RSA2 | 固定值RSA2 |
版本 | version | Y | String | 1.0 | 固定值1.0 |
编码 | charSet | Y | String | utf-8 | 固定值utf-8 |
随机字符 | nonce | Y | String | dsafsadfasdfasa | 随机字符串 |
商户订单号 | mchNo | Y | String | ||
交易订单号 | tradeNo | Y | Long | 98604945716***** | 协议号 |
协议状态 | status | Y | String | 状态 1 已完成 2 已解约 | |
变更时间 | changeTime | Y | String | 2022-03-23 19:17:56 | yyyy-MM-dd HH:mm:ss |
- 返回参数说明:
SUCCESS
通知示例代码:
{
"mchNo": "4fcf9d5e-4891-4e",
"tradeNo": 1066044865591424,
"status": "2",
"changeTime": "2023-03-02 10:48:56",
"appId": 233799539766016,
"mchId": 233799539766016,
"sign": "GP*********=",
"signType": "RSA2",
"version": "1.0",
"charSet": "UTF-8",
"nonce": "ft2u016vvo"
}
7.10. 错误码定义
以下为接入过程中高频错误码,可供参考。
结果码 | 说明 | 建议 |
500 | “verify signature failure” | 签名验证失败,检查签名信息(密钥、入参) |
500 | “nonce str verify failure” | nonce验证失败,时间戳与nonce每次请求不能重复 |
500 | “unauthorizedException” | 重新获取token |
400 | “系统异常” | 检查“productId”或“userId、userAccount"是否超长 |
500 | “商户订单信息错误” | 检查“productCode” |
500 | “Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported” | 检查参数及代码,确定类型 |
500 | “参数错误” | 检查是否缺失传参 |
8. 其他说明
8.1. 商户订单号
商户支付的订单号由商户自定义生成,仅支持使用字母、数字、中划线-、下划线_、竖线|、星号*这些英文半角字符的组合,请勿使用汉字或全角等特殊字符。订阅服务平台要求商户订单号保持唯一性,同一个应用ID不可重复(建议根据当前系统时间加随机序列来生成订单号)。重新发起一笔支付要使用新订单号,避免重复支付。
8.2. 金额
交易金额默认为人民币交易,接口中参数支付金额单位为【分】,支付金额必须大于 0,参数值不能带小数,暂不支持外币交易。
8.3. 日期时间
标准北京时间,时区为东八区;如果商户的系统时间为非标准北京时间。按照各个接口需要日期时间格式的传入。
8.4. 签名算法
开发者需要自行实现签名,签名过程如下:
1. 筛选并排序
获取所有请求参数,不包括字节类型参数,如文件,字节流,剔除sign字段,剔除值为空的参数,并按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。
2. 拼接
将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。
例如:appId=2018071360678029&sign_type=RSA2&version=1.0
3. 签名
使用各自语言对应的SHA256WithRSA(对应signType为RSA2)签名函数利用开发者私钥对待签名字符串进行签名。
4. 把生成的签名赋值给sign参数,拼接到请求参数中.
5. 验签工具:联想验签工具
9. 版本更新日志
9.1. V2.3.3内容变更
变更项 | 接入说明 |
1、PC端订阅SDK收银台样式更新 | 所有CP需接入联想统一支付收银台 |
2、修复部分登录、支付问题 | CP需更新SDK |
3、优化deviceId重复、变更等相关问题 | 针对已接入设备版订阅的应用,CP需重新集成SDK |
10. 常见问题
本模块整理了接入流程“申请参数(appid&key)→接入订阅SDK(登录+支付)→接入退款API→测试→发布应用”各环节的常见问题,开发者可按遇到问题的环节进行查找。
10.1. 申请参数
Q:为什么需要开发者提供退款通知地址?
A:订阅服务支持用户在”联想应用商店-订单中心“进行7天无理由退款,当用户退款完成后,联想服务器会发送退款通知给开发者,开发者需对通知的参数进行处理,变更用户权益,因此需开发者提供一个用于接收退款通知的url
Q:应用内无退款入口,是否还需要提供退款通知地址?
A:需要,用户可在联想应用商店进行退款
Q:退款通知地址是否可以先提供测试环境的,待应用上线时再更改为正式的地址?
A:可以
10.2. 接入订阅SDK
Q:SDK目前支持的语言版本?
A:目前支持C++、C#,其他版本开发中
Q:帐号登录窗口是SDK弹的吗?
A:是的,SDK弹出的登录窗口包含了登录、注册、更改密码等功能
Q:用户必须使用联想帐号登录吗?
A:订阅服务中用户须使用联想帐号登录
Q:开发者是否需要为用户在自己的帐号体系中生成一个新帐号并自动绑定联想的帐号?
A:开发者可生成一个对用户不可见的帐号,或者不生成,直接使用登录SDK提供的唯一ID作为用户的唯一标识
Q:使用token获取lenovoID,开发者是通过应用客户端还是应用服务器去请求联想服务器获取?
A:考虑到通信的安全性,建议开发者尽可能通过应用的服务端去获取
Q:用户登录token是24小时过期吗?24小时过期,用户是否需要频繁登录?
A:为了保护用户帐号安全,token是24小时过期,但对已登录用户没有影响,无需用户频繁登录
Q:用户token如果过期,支付时是否需要用户重新登录?
A:无需用户重新登录,开发者可在调用支付接口前使用客户端提供的“获取最新token接口”获取token
Q:登录的接口中,入参的格式是json字符串吗?
A:是的
Q:登录的接口中,key值使用平台公钥还是应用私钥?
A:应用私钥
10.3. 接入退款API
Q:开发者的应用内是否需要支持用户退款?
A:建议开发者在应用内支持用户退款,若不支持,用户也可在联想应用商店内进行退款
Q:接入订阅服务后,开发者的微信/支付宝支付是否需要屏蔽,改为订阅服务的支付?
A:是的
Q:是否有接口查询支付结果和退款结果?
A:支付结果可通过接入文档中的“查询支付结果接口”查询,退款结果暂无查询接口,开发者可在退款通知中查询退款状态。
10.4. 测试
Q:支付和退款是否有测试环境?
A:无测试环境,开发者可将商品设为一个较低的金额(比如1分钱)进行测试。
10.5. 发布应用
Q:订阅版本的应用如何发布?
A:参与联运的应用首次上架及后续版本更新,开发者需将应用包发送给联想接口人,由联想进行配置上架。
Q:非联运版本的应用如何处理?
A:线上非联运版本的应用支持开发者保留和更新,这样可以满足用户的包体更新,主要依据开发者的诉求来决定是联运和非联运版本并行,还是主推联运版。
原文地址:https://open.lenovomm.com/developer/doc?id=1672841326302597121
联想开放平台地址:联想开放平台
联想开发者专属QQ客服(工作日9:30-18:00):联想开发平台首页右侧悬浮的在线客服聊天入口可直接会话,无需添加好友。也可搜索官方客服QQ号2881414004。
联想应用商店微信公众号: