9 Payment system¶
Pico payment is a game currency payment system based on the Pico account system, and the settlement method is based on the current game currency unit under Pico (P currency). If the project requires internal purchasing function, please read the contents of this chapter carefully. If the project does not require internal purchasing function, please ignore this chapter.
9.1 Preparations¶
9.1.1 Required parameters¶
- APP ID : Unique identifier generated by the developer platform for the application
- APP KEY : Key generated by the developer platform for the application
- APP Secret : Key generated by the developer’s platform for the application to be used for payment
- Publisher ID : Developer’s ID, also called Merchant ID
9.1.2 Apply to become a Pico developer¶
When developers access the payment SDK, they need to create an application in the developer platform and get the application parameters, the corresponding process is as follows.
- Log in to the developer platform and register a Pico member (http://developer.pico-interactive.com/)
- In the “Account” pop-up window, select the region where your account is located: Mainland China / Other regions and click the “Register” button.
- Verify your account
- If you selected “Mainland China”, you need to fill in your mobile phone number and set your password to verify your mobile phone number via SMS.
- If you choose “Other regions”, you need to fill in your email address and set your password, select the country/region where your account is located, and verify your email account by verification email.
- Check the box to accept Pico’s User License Agreement and Privacy Policy, then click the Sign Up button.
- Congratulations! You are now officially a Pico developer.
9.1.3 Get application parameters¶
- Get Publisher ID
After applying as a developer, enter the developer management platform, click on “My Apps”, enter “Apps” and click on the “API” menu to see the developer ID, which will be used as a unique mark for merchants in the payment system.
- Create application, select payment type
Developers can enter the application creation phase from the management center. After clicking Create Application, then enter the corresponding platform to perfect the relevant information of application:
Figure 9.1 Information about application improvement
Note: Please fill out the application type carefully, and it can’t be modified once you fill it out!
- Select the application. Pay directly using the P-Coin amount.
- Select the game. Use product code to pay. Product code is the unique identification of developer’s backend for paid props in the game.
For in-app purchase of game applications, developers should add commodity codes for unified management in the developer backend.
The product code payment configuration interface is as follows:
Figure 9.2 In-app purchase configuration
Product code definition rules:
- The first digit is a letter, only letters and numbers are allowed, and it should be no more than 20 characters
- The product code between different props cannot be repeated
- The prop types are divided into consumable prop and non-consumable prop. Consumable prop are reusable commodities, such as gold currency, blood bottle, etc. Non-consumable props are disposable purchased products, such as weapons and unlocking levels.
- Get APP ID, APP KEY, APP Secret parameters
After successfully creating an application, the developer platform will assign APP ID, APP KEY, APP Secret parameters for the current application.
Figure 9.3 APP ID、APP KEY、APP Secret
Now, you have obtained Publisher ID, APP ID, APP KEY and APP Secret.
9.1.4 Configure application parameters in AndroidManifest.xml¶
- Select File - Build Setting to enter the following interface, then click the Android icon.
- Enter Player Settings - Publishing Settings to select Custom Main Manifest, then you will find a new AndroidManifest.xml in this path: Assets - Plugins - Android
Configure the application parameters as follow:
- Add the necessary permissions:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
When selecting “China” as “Release area”, the following segments need to be included in Android Manifest:
<!--APPID-->
<meta-data
android:name="pico_app_id"
android:value="APP ID"/> //Replace APP ID with the APPID applied by the application<!--APPKEY-->
<meta-data
android:name="pico_app_key"
android:value="APPK EY"/> //Replace APP KEY with APPKEY applied by the application<!--Authorization Scope-->
<meta-data
android:name="pico_scope"
android:value="SCOPE"/> //Replace the SCOPE with the fixed value get_user_info<!--Developer ID-->
<meta-data
android:name="pico_merchant_id"
android:value="DEVELOPER ID"/> //Replace DEVELOPER ID with the Developer's DEVELOPER ID<!--Payment Key-->
<meta-data
android:name="pico_pay_key"
android:value="APP SECRET"/> //Replace APP SECRET with APPSECRET applied by the application
When selecting regions other than “China” as “Release area”, the following segments need to be included in Android Manifest:
<!--APPID-->
<meta-data
android:name="pico_app_id_foreign"
android:value="APP ID"/>
<!--APPKEY-->
<meta-data
android:name="pico_app_key_foreign"
android:value="APPK EY"/>
<!--Authorization Scope-->
<meta-data
android:name="pico_scope_foreign"
android:value="SCOPE"/>
<!--Developer ID-->
<meta-data
android:name="pico_merchant_id_foreign"
android:value="DEVELOPER ID"/>
<!--Payment Key-->
<meta-data
android:name="pico_pay_key_foreign"
android:value="APP SECRET"/>
Note that selecting “Global” will include all regions, so both segments are required as follow:
Example:
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unity3d.player"
xmlns:tools="http://schemas.android.com/tools">
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application>
<activity android:name="com.unity3d.player.UnityPlayerActivity"
android:theme="@style/UnityThemeSelector">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
<!-- China Region -->
<!--APP ID-->
<meta-data
android:name="pico_app_id"
android:value="Your_APP_ID"/>
<!--APP key-->
<meta-data
android:name="pico_app_key"
android:value="Your_APP_KEY"/>
<!--SCOPE-->
<meta-data
android:name="pico_scope"
android:value="get_user_info"/> <!--fixed value-->
<!--Published/Merchant ID-->
<meta-data
android:name="pico_merchant_id"
android:value="Your_DEVELOPER_ID"/>
<!--APP Key-->
<meta-data
android:name="pico_pay_key"
android:value="Your_PAY_KEY"/>
<!-- Other Regions -->
<meta-data
android:name="pico_app_id_foreign"
android:value="Your_APP_ID"/>
<meta-data
android:name="pico_app_key_foreign"
android:value="Your_APP_KEY"/>
<meta-data
android:name="pico_scope_foreign"
android:value="get_user_info"/>
<meta-data
android:name="pico_merchant_id_foreign"
android:value="Your_DEVELOPER_ID"/>
<meta-data
android:name="pico_pay_key_foreign"
android:value="Your_PAY_KEY"/>
</application>
</manifest>
Note:
- pico_pay_key/ pico_pay_key_foreign corresponds to AppSecret on the developer platform.
- pico_scope and pico_scope_foreign are fixed values get_user_info
- Android 9 and above, network requests must use https, and the current default and initserver configuration are http, the system will intercept the error. Repair method: Add the following content in application label of AndroidManifest.xml:
android:usesCleartextTraffic="true"
9.2 Use of payment system¶
9.2.1 Create prefab and callback script.¶
- Create a prefab called PicoPayment. The name must be this one.
- Create a script to define the APIs and add it to PicoPayment prefab. The name of the APIs cannot be changed:
public class Callback : MonoBehaviour
{
// Login callback
public void LoginCallback(string LoginInfo)
{
...
// Refer to chapter 9.3.1
}
// Callback of Getting suer info
public void UserInfoCallback(string userInfo)
{
...
// Refer to chapter 9.3.2
}
// Callback of query order
public void QueryOrPayCallback(string queryOrPayInfo)
{
...
// Refer to chapter 9.3.3 and 9.3.4
}
// Required
public void ActivityForResultCallback(string activity)
{
PicoPaymentSDK.jo.Call("authCallback", activity);
}
}
The process of payment system is relatively complicated, and we have specially provided a Demo for reference. Expand Assets> Pxr_Payment>Demo>Scenes to open the Demo scenes as follows:
Figure 9.6 Payment of Demo
9.2.2 Complete the APIs¶
The returned parameters are in Json format, for details please refer to Chapter 9.3
9.2.3 Usage process¶
- Call Login() first and after successful login, call GetUserAPI() and Pay()
- Sample code:
Login and Pay method calls:
public class PicoDemo
{
// 1. Login, call PicoPaymentSDK.Login
public static void Login()
{
PicoPaymentSDK.Login();
}
// Get user information,call PicoPaymentSDK.GetUserAPI()
public static void GetUserAPI()
{
PicoPaymentSDK.GetUserAPI();
}
// 4.Pay ,call PicoPaymentSDK.Pay(payorderJson)
// For parameters in payorderJson,please refer to chapter 9.3.3
public static void Pay(string payorderJson)
{
PicoPaymentSDK.Pay(payorderJson);
}
// 6.Check payment order
public static void queryOrder(string orderId)
{
PicoPaymentSDK.QueryOrder(orderId);
}
}
Callbacks for payment:
public class Callback : MonoBehaviour
{
private static string IS_SUCCESS = "isSuccess";
private static string MSG = "msg";
private static string CODE = "code";
// Flag bit for successful login
private static string LOGIN_SUCCESS = "true";
// Flag bit for failed login
private static string LOGIN_FAIL = "false";
// Get returned status flag bit of getting user information interface
private static string GET_USER_RET_CODE = "ret_code";
// Get returned status info of getting user information interface
private static string GET_USER_RET_MESSAGE = "ret_msg";
// Get returned content flag bit of getting user information interface
private static string GET_USER_RET_DATA = "data";
// Get returned successful status flag bit of getting user information interface
private static string GET_USER_SUCCESS = "0000";
// Value of successful payment
private static string PAY_SUCCESS = "12000";
// Value of successful query order
private static string QUERY_ORDER_SUCCESS = "13000";
// 2. Callback for login
public void LoginCallback(string LoginInfo)
{
JsonData jsrr = JsonMapper.ToObject(LoginInfo);
JsonData loginStatus = jsrr[IS_SUCCESS];
if(loginStatus != null && LOGIN_SUCCESS.Equals(loginStatus.ToString()))
{
Debug.Log("Login Success." + LoginInfo);
// 3. Call GetUserAPI or Pay
PicoDemo. GetUserAPI();
return;
}
Debug.Log("Login fail." + LoginInfo);
}
// 4. Callback for get user info
public void UserInfoCallback(string userInfo)
{
if(userInfo != null)
{
JsonData jsrr = JsonMapper.ToObject(userInfo);
JsonData retCode = jsrr[GET_USER_RET_CODE];
if (retCode != null && GET_USER_SUCCESS.Equals(retCode.ToString()))
{
JsonData data = jsrr[GET_USER_RET_DATA];
string userName = data["username"].ToString();
return;
}
}
Debug.Log("getUserInfo fail , " + userInfo);
}
// 5.Callback for query order
public void QueryOrPayCallback(string queryOrPayInfo)
{
if (queryOrPayInfo != null)
{
JsonData jsrr = JsonMapper.ToObject(queryOrPayInfo);
JsonData code = jsrr[CODE];
JsonData msg = jsrr[MSG];
// successful payment
if (code != null && PAY_SUCCESS.Equals(code.ToString()))
{
Debug.Log("Pay successs.");
return;
}
// successful query order
if (code != null && QUERY_ORDER_SUCCESS.Equals(code.ToString()))
{
Debug.Log("Query order successs.");
// get status of order
JsonData tradeStatus = msg["trade_status"];
// get order number
JsonData outTradeNo = msg["out_trade_no"];
if (tradeStatus !=null && "SUCCESS".Equals(tradeStatus.ToString()))
{
Debug.Log("Order id" + outTradeNo.ToString() + " result is success ");
}
return;
}
}
Debug.Log("QueryOrPayCallback fail , " + queryOrPayInfo);
}
// Required function
public void ActivityForResultCallback(string activity)
{
PicoPaymentSDK.jo.Call("authCallback", activity);
}
}
Sample Demo:
9.3 Function interface explanation¶
Callback functions are implemented in “Callback.cs” script file from Pico_Payment folder. Details can be found in Chapter 9.2
9.3.1 Login¶
Login¶
Function name: public void Login()
Functions: login
Parameter: None
Return value: None
Method of calling: PicoPaymentSDK.Login();
LoginCallback¶
Function name: public void LoginCallback(string LoginOrUserInfo)
Functions: login callback
Parameter:
- string LoginOrUserInfo:Login information returned to the background
// success
LoginOrUserInfo = {"isSuccess":"true","msg":"message"}
// failed
LoginOrUserInfo = {"isSuccess":"false","msg":"message"}
Return value: None
Method of calling: LoginOrUserInfoCallback(string LoginOrUserInfo)
Message of login failed:
Error Information | Instruction | Cause |
---|---|---|
TIME_ERROR | Timestamp error | The difference between local time and server time is more than 8 minutes |
SYSTEM_USER_NOT_FIND_ERROR | User not found | The user corresponding to the token in user center is notfound |
SYSTEM_USER_TOEKN_NOT_FIND_ERROE | Failed to find user token | The user corresponding to the token in user center is notfound |
SYSTEM_USER_TOKEN_CHECK_FAILURE_ERROR | Failed to verify user token | User center token invalidation |
SYSTEM_USER_TOKEN_UNKNOWN_ERROR | Failed to verify user token | Failed to verify user token |
APP_CHECK_ERROR | Failed to verify application | APP ID and other parameters are filled in incorrectly |
9.3.2 Get user information¶
GetUserAPI¶
Function name: public void GetUserAPI()
Functions:Get user information
Parameter:None
Return value:None
Method of calling:PicoPaymentSDK.GetUserAPI();
UserInfoCallback¶
Function name: public void UserInfoCallback(string userInfo)
Functions:Callback of getting user information
Parameter:
- string userInfo: The user information returned by the user center server, a string in Json format
Where OpenId is the unique identification of the user in the current application.
The parameter format is as follows:
{
"ret_code":"0000",
"data":{
"aboutme":"",
"birthday":1460476800000, //long type value, date of birth, default time is zero on a certain day of a certain month of a certain year
"phone":"13100000000", //Phone number
"username":"Admin", //User Name
"email":"", //Email
"gender":"male",
"lastname":"",
"openid":"4f3148bdc34d9bca104927729a173b64", //OpenId
"firstname":"",
"avatar":"http://172.31.83.11/upload/123123123",
"country":"China", //Country
"city":"" //City
},
"ret_msg":"调用成功"
}
Returned value:None
9.3.3 Payment¶
Pay¶
Function name: public void Pay(string payOrderJson)
Functions: payment
Parameter:
- payOrderJson:Json string containing order information, the following parameters are required:
subject | Order title and product overview |
body | Description of order and detailed description of goods |
order_id | The order number generated by the developer, which shall not exceed 64 characters. |
total | The total price of the commodity is an integer greater than 0. It is entered when paying directly, but is not entered when paying by the product code. |
goods_tag | Commodity label |
pay_code | The product code must be consistent with the configuration of the developer platform. It is entered when paying directly, but is not entered when paying by the product code. |
Return value: None
Method of calling: void Pay(string payOrderJson)
Example 1 :
Pay ("{'subject': 'Game',' body ': Buy a complete game', 'order_id ':'10000','total ':'10','goods_tag ':'game' }");
Example 2 :
Pay ("{'subject': 'Game',' body ': Buy a complete game', ,'order_id ':'10000',','goods_tag ':'game','pay_code':'123' }");
Note: Example 1 is a direct payment method and Example 2 is a payment method using the product code. The two methods cannot be carried out at the same time. The attribute of the product code cannot be filled in during direct payment and the total attribute cannot be filled in or set to 0 when using a product code. The two payment methods must be consistent with the payment types set in the developer platform account.
Note: order_id is unique in the payment system. Therefore, in order to ensure that a single user has a unique order number on the same app, it needs to be named in the form of “openID + custom order number”.
QueryOrPayCallback¶
Function name: public void QueryOrPayCallback(string queryOrPayInfo)
Functions: payment callback
Parameter:
- string queryOrPayInfo:payment information returned to the background
For example:
{“code “:”12000”,”msg”:”PAY_SUCCESS”} //When code value is 12000, PAY_SUCCESS;others are exception code for payment failure
Payment callback code&msg list:
Code | Msg |
---|---|
11001 | USER_NOT_LOGIN_OR_EXPIRED |
11004 | MISSING_APP_PARAMETERS |
12000 | PAY_SUCCESS |
12002 | ENTER_AMOUNT_ERROR |
12003 | PCOIN_NOT_ENOUGH |
12006 | NOT_ENTER_ORDER_INFO |
12007 | PAY_ORDER_EXIST |
12008 | PAY_CODE_NOT_EXIST |
12009 | PAY_CODE_ALERADY_CONSUMED |
14001 | SDK_LOCAL_ERROR |
14004 | NETWORK_ERROR |
15001 | SYSTEM_ERROR |
15003 | SERVICE_APP_PARAMETER_NOT_MATCH |
Return value: None
Method of calling: QueryOrPayCallback(string queryOrPayInfo)
9.3.4 Order query¶
QueryOrder¶
Function name: public void QueryOrder(string orderId)
Functions: querying order
Parameter: orderId: order number
Return value: None
Method of calling: QueryOrder(string orderId)
QueryOrPayCallback¶
Function name: public void QueryOrPayCallback(string queryOrPayInfo)
Functions: querying order callback
Parameter:
- string orderId: queryOrPayInfo: order information returned to the background
For example:
code: When the value is 13000, the query order is successful. Other exception codes for payment failure
msg: When the code is 13000, msg returns the json string of order information; when the code is other values, return to the corresponding description information
{"code ":"13000","msg":"json string of user information "}
When the code is 13000, the json string of msg’s order information is as follows:
{
"trade_no":"22016082314719505878171324",// Pico Payment order No.
"open_id":"4f3148bdc34d9bca104927729a173b64",
"ret_msg":"",
"coupon_fee":0.00,
"fee_type":"PIC",
"pay_time":1471950587000,// Payment completion time
"nonce_str":"yiUzuv4VQO1OXBAzVyZSRztOmRgIOioT",
"out_trade_no":"12345678903", // Order number of merchant
"trade_status":"SUCCESS",//SUCCESS—Successful payment
"trade_type":"EGG",
"result_code":"SUCCESS",
"mch_id":"company_id",
"ret_code":"SUCCESS",
"sub_msg":"OK",
"total_fee":100.00,// Total order amount
"app_id":"bf18ac2de375095d63428134e44d1867",
"sub_code":"SUCCESS",
"receipt_fee":100.00,// Paid-in amount
"buyer_pay_fee":100.00// The amount paid by the buyer
}
When code is other values, the json string of msg’s order information is as follows:
{"code ":"13006","msg":"QUERY_ORDER_NOT_EXIST"}
Code and msg for query:
Code | Msg |
---|---|
11001 | USER_NOT_LOGIN_OR_EXPIRED |
13000 | QUERY_ORDER_SUCCESS |
13003 | NOT_ENTER_ORDER_ID |
13006 | QUERY_ORDER_NOT_EXIST |
14001 | SDK_LOCAL_ERROR |
14004 | NETWORK_ERROR |
15001 | SYSTEM_ERROR |
15003 | SERVICE_APP_PARAMETER_NOT_MATCH |
Return value: None
Method of calling: QueryOrPayCallback(string queryOrPayInfo)
9.4 Developer server interaction¶
After the payment is completed, the payment system will send the relevant payment results and user information to the merchant, and the merchant needs to receive and process them and return a response.
When the background notifies the interaction, if the receiving of the merchant’s response by the payment system receives is not successful or overtime, the notification should be considered as failed, and the payment system will periodically re-initiate the notification through certain policies to maximize the success rate of the notification, but it may not guarantee that the notification will be eventually successful.
The same notification may be sent to the merchant system repeatedly and the merchant system must be able to process duplicate notifications correctly. The recommended practice is to firstly check the status of the corresponding service data when it receives and processes the notification, and determine whether the notification has been processed, it should be re-processed if it has not been processed, and the result return will be successful directly if it has been processed. Before the status check and processing of business data, data locks should be used for concurrency control to avoid data confusion caused by function reentry.
The merchant server needs to implement the following interface for receiving the request from the Pico server and get the payment result and user information of the Pico payment system:
Name | Payment result callback interface |
Request type | POST |
Request URL | Pay, parameter notify_url transmitted by PayOrder |
Request format | JSON |
Return format | JSON |
Is login required | Yes |
Request parameter | For details, see the following “Table 9.1 Returned parameter information in the payment result notification” |
Return Params | See details in “Table 9.2 Returned Results” below |
Return parameter example | { “ret_code”:”SUCCESS”, “ret_msg”:”OK” } |
Update instruction |
Table 9.1 Returned parameter information in the payment result notification
Field Name | Param Name | Required | Type | Description |
---|---|---|---|---|
Return Status Code | ret_code | Yes | String | SUCCESS/FAIL This field is a notification identification, not a trade identification. The result_code is used to determine whether a trade is successful. |
Error Code | ret_msg | No | String | Return message, if not empty, then it’s the reason of the error: Name failed – param format validation error |
Error Code | sub_code | No | String | Error code |
Error code description | sub_msg | No | String | The description of the error return message |
Pico pay order number | trade_no | Yes | String | Pico payment order number |
Merchant order number | out_trade_no | Yes | String | The internal order number in merchant system |
App ID | app_id | Yes | String | The app APP_ID that the platform has audited |
Merchant ID | mch_id | Yes | String | The merchant ID that the payment assigned |
User ID | open_id | Yes | String | The unique ID of the user under the merchant’s appid |
Device ID | device_id | No | String | The ID of the terminal device |
Random string | nonce_str | Yes | String | Random string, less than 32 chars. Random number generation algorithm is recommended. |
Name | signature | Yes | String | Name, see details in Name generation algorithm. |
Business Result | result_code | Yes | String | SUCCESS/FAIL |
Trade Type | trade_type | Yes | String | Payment type |
Currency Type | fee_type | Yes | String | Currency type |
Total Fee | total_fee | Yes | String | Total order amount |
Actual Fee | receipt_fee | Yes | String | Actual Fee |
The amount of the fee the buyer paid | buyer_pay_fee | No | String | The amount of the fee the buyer paid |
Voucher or Discount Amount | coupon_fee | No | String | Vouchers or Discount Amount |
Merchant Packet | attach | No | String | Merchant packets, returning as-is |
Pay completion time | pay_time | Yes | String | The time that payment completes, the format is “yyyy-MM-dd HH:mm:ss” |
Table 9.2 Returned Results
Field Name | Param | Required | Type | Description |
---|---|---|---|---|
Return status code | ret_code | Yes | String | SUCCESS/FAIL SUCCESS represents that the merchant has successfully received notification and validated. |
Return message | ret_msg | No | String | Return message, if not empty, then it’s the reason of error: Name failed - parameter format validation error |
Special remarks: The signature verification must be performed for the contents of the payment result notification in the merchant system to prevent “false notification” due to data leakage and capital loss.
The function name verification rule is as follows:
- Remove the signature parameter from the returned list of parameters, and simultaneously add key = “app_secret”, value=paykey, then sort it naturally according to the key value, separate the multiple parameters with &, and finally take MD5 encryption
- Compare the encrypted string with the get signature
The name of the function is as follows:
/**
* result: the map collection of retrieved data
* paykey: It’s the paykey on the developer platform
*/
/**
public static String createSign(Map<String, Object> result, String paykey)
{
if (result == null || result.size() == 0)
return null;
result.put("app_secret", paykey); //1.Add key = “app_secret”, value=payke
String sign = result.get("Name");//2.Save Name value, to be used for validation
result.remove("Name"); //3.remove Name parameter
String[] tmp = new String[result.size()];
int i = 0;
for (String key : result.keySet())
{
tmp[i++] = key;
}
Arrays.sort(tmp); //4.natural sort
String signTemp = "";
for (String string : tmp)
{
if (result.get(string) == null)
continue;
signTemp += string + "=" + URLEncoder.encode(result.get(string).toString()
, "utf-8") + "&";
}
if (signTemp.endsWith("&"))
signTemp = sign.substring(0, signTemp .length() - 1);
Log.i(TAG, "createSign: " + signTemp );
String localSign = MD5.MD5(sign); //5.generate MD5 encrypted string
return localSign.equal(sign);//6.validate with “sign” in 2
}