Games Developer Team

## Game System Integration Document - Seamless Mode
sample:
    curl 'https://APIDOMAIN/api/v1/game/launch' \
    -H 'appid: ***' \
    -H 'appsecret: ********-****-****-****-************' \
    -H 'content-type: application/json' \
    -H 'AppDataCryptic: 1' \
    --data-raw xxtea.encrypt('{"UserID":"user123","GameID":"gameid39","Language":"th","ReturnUrl":""}', <AppXXTEAKey>)

Game System Integration Document

## APIs Provided by Platform

### Data encryption transmission (optional)
-  After specifying the Header 'AppDataCryptic: 1', the body of all requests/responses should be encrypted and decrypted with the xxtea encryption key provided by us
-  **It is recommended to use data encryption requests**

Cryptographic reference:
```
    // sample
    str := "Hello World"
    key := "1234567890abcdef"
    encrypt_data := Encrypt([]byte(str), []byte(key))
    fmt.Println(encrypt_data)
    // output: [128 95 225 93 56 5 238 36 34 206 165 148 8 186 172 142]
```
  ```
    import github.com/xxtea/xxtea-go/xxtea

	xxteakey := []byte("12345") 
	payload := xxtea.Encrypt([]byte(`{"Provider":""}`), xxteakey)  
	req, _ := http.NewRequest(http.MethodPost, "https://APIDOMAIN/api/v1/game/list", bytes.NewReader(payload)) 
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("AppId", "FAKEAPPID")                                
	req.Header.Set("AppSecret", "b6337af9-a91a-4085-b1f2-466923470735") 
	req.Header.Set("AppDataCryptic", "1")                               
	respbody, _, _ := ut.DoHttpReq(http.DefaultClient, req)
	respbody = xxtea.Decrypt(respbody, xxteakey) 
	os.Stdout.Write(respbody)

	/**
	output:
	{"code":0,"error":"","data":{"List":[{"ID":"gameid_10001","Name":"Treasure of Sudamérica","Type":0,"IconUrl":"https://ICONDOAMIN/icon/app/gameid_10001.png","WebIconUrl":"https://ICONDOAMIN/icon/web/gameid_10001.png","Provider":""}, ...more}}
	*/
  ```


### Interface Protocol

- Set request data Header to Content-Type: application/json

- All interfaces use POST

- Return JSON in a fixed style {"code":int, error: string, data: object}, where a non-zero code field indicates an error occurred, in which case the data field should not be used further. code:0 indicates success, when code is not 0, error represents the error description

1. ```{"code":0,"error":"","data":{"Balance":245.9}}```

2. `{"code":1008,"error":"player not found","data":null}`

- Use RFC3339 format for datetime

Note: Only need to focus on the /api/v1/ interfaces

### Interface List
- Add 2 Headers to all request interfaces
    1. AppID: appid information provided by us
    2. AppSecret: secret information provided by us

- The API URL, AppID, and AppSecret in the document will be provided by us after you apply for the line.

API URL: https://APIDOMAIN/
AppID: XXXXGame
AppSecret: 2de5c9c3-76a2-428a-aba0-XXXXXXXXXXXX

## API Interfaces

[error list](/html-transferWallet.html?name=errors.md)

### Create Player Account (optional)

> URL: APIURL/api/v1/player/create

#### Request Parameters:

| Parameter Name | Type | Description |
| :--- | :---: | :---: |
| UserID | string[4-40] | Operator's unique identifier |

- Optional interface, automatically created when the player starts the game for the first time

- e.g. ```{"UserID":"abc"}```

#### Return Result:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| Pid | int64 | Platform's unique player identifier |

-  e.g. `{"code":0,"error":"","data":{"Pid":102392}}`

- Repeatedly creating the same player returns the same result

- /api/v1/player/transferIn and /api/v1/game/launch will automatically create the player

### Get Player's Current Unsettled Game List

> URL: APIURL/api/v1/player/pendinggames

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| UserID | string[4-40] | Operator's unique player identifier |

- e.g. ```{"UserID":"user_id"}```

#### Return Result:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| Balance | float | Player's current available balance |
| PendingGames | string[] | List of games that have been bet on but not yet
settled |

- [Balance] only has meaning in transfer mode, [PendingGames] returns the list of games that have been bet on but have not yet completed all settlements

- e.g.
    ```{"code":0,"error":"","data":{"Balance":4259.94,"PendingGames":["lottery","gameid_1489936","gameid_20","gameid_37","gameid_58","gameid_59","gameid_94"]}}```

### Get Game List

> URL: APIURL/api/v1/game/list

#### Request Parameters:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| Provider   | string    | game provider     |

- eg. ```{}``` //pass in an empty json object, will return all games
- eg. `{"Provider": "pg"}` // only return pg provider games

#### Return Result:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| ID | string | Game ID |
| Name | string | Game Name |
| Type | int | 0: Slot game, 1: Fishing game, 2: Hundred game,  3: Board game, 4: Lottery game |
| IconUrl | string | Game icon |
| Provider   | string    | game provider     |

-   eg. ```{"code":0,"error":"","data":{"List":[{"ID":"gameid_10004","Name":"Ghost Rider","Type":0,"IconUrl":"https://ICONDOMAIN/icon/app/gameid_10004.png","WebIconUrl":"https://ICONDOMAIN/icon/web/gameid_10004.png","Provider":""},{"ID":"gameid_10003","Name":"Stellar Candy Odyssey","Type":0,"IconUrl":"https://ICONDOMAIN/icon/app/gameid_10003.png","WebIconUrl":"https://ICONDOMAIN/icon/web/gameid_10003.png","Provider":""}, ...]}}```

### Get Game Login URL

> URL: APIURL/api/v1/game/launch

#### Request Parameters:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| UserID | string[4-40] | Operator's unique player identifier |
| GameID | string | Game ID |
| Language | string | Language setting th, en ... |
| ReturnUrl| string| **optional** Set the URL to be redirected after exiting the game, closing the current page by default |
| ShowPGVerify | bool | **optional** Whether to display the *PG* verification dialog box, default false |


> en English
> da Danish
> de German
> es Spanish
> fi Finnish
> fr French
> id Indonesian
> it Italian
> ja Japanese
> ko Korean
> nl Dutch
> no Norwegian
> pl Polish
> pt Portuguese
> ro Romanian
> ru Russian
> sv Swedish
> th Thai
> tr Turkish
> vi Vietnamese
> my Burmese
> in  India
> ta  Tamil
> ms  Malaysia
> bn  Bengal
> urIN Urdu
> grGR Greece 
> zh Chinese 
> ko  korean

- e.g.
    ```{"UserID":"operator_user_abcd","GameID":"gameid_001","Language":"th"}```

#### Return Result:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| Url | string | Game login URL |

- e.g.
    ```{"code":0, "error":"","data":{"Url":"https://LAUNCHDOMAIN/gameid_001/index.html?l=th&t=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxODcsIlMiOjEwMDIsIkQiOiJYaW5nWXVuWGlhbmcifQ.9td-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxmw"}}```

### Pull Betting History

> URL: APIURL/api/v1/record/betlist

#### Request Parameters:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| StartID | string[24] | Starting bet record ID, the result will not include this ID record |
| Count | int | Number to pull, range 1~100,000, exceeding will be limited to 1 or 100,000 |

- e.g. ```{"StartID":"656fd4993be8edaa4d37830e","Count":2}```

    When continuously pulling, pass in the ID of the last record from the previous pull for the next pull. When StartID is empty "", it will return from first record
	Limit the number of pulls to one within 10 seconds

#### Return Result:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| ID | string | Unique ID of the record, sorted in ascending order |
| Pid | int64 | Platform player ID |
| UserID | string | Operator player ID |
| GameID | string | Game ID |
| Bet | float | Total bet amount |
| Win | float | Total win amount |
| WinLose | float | Net win/loss |
| InsertTime | datetime | Time the record was stored |
| AppID | string | Operator ID |
| Balance | float | Balance after settlement |
| Grade | int | Bet level |
| RoundID    | string   | Round ID, this field is the same for the same game  |
| GameType | int | 0: Slot game, 1: Fishing game, 2: Hundred game,  3: Board game, 4: Lottery game |
| BonusCode   |  string     | frb code, A non-empty string indicates a gift |

- eg. ```{"code":0,"error":"","data":{"Title":["ID","Pid","UserID","GameID","Bet","Win","InsertTime","AppID","Balance","WinLose","Grade","RoundID","GameType","BonusCode"],"List":[["68b7984ddb1ee4d27f75835c",102673,"testSJ06","gameid_1897678",15,0,"2025-09-03T09:22:21.891+08:00","FAKEAPPID",828,-15,60,"68b7984ddb1ee4d27f75835b",0,""],["68b7985fdb1ee4d27f75835e",102673,"testSJ06","gameid_1897678",15,0,"2025-09-03T09:22:39.772+08:00","FAKEAPPID",813,-15,60,"68b7985fdb1ee4d27f75835d",0,""],["68b79862db1ee4d27f758360",102673,"testSJ06","gameid_1897678",15,0,"2025-09-03T09:22:42.734+08:00","FAKEAPPID",798,-15,60,"68b79862db1ee4d27f75835f",0,""]],"NextStartID":"68b79862db1ee4d27f758360"}}```

### Get the bet details display page URL

>   URL: APIURL/api/v1/gamedetailsurl/get

#### Request Parameters:

| Parameter Name  | Type       | Description                                           |
|---------|------------|------------------------------------------------|
| BetLogID | string[24] | bet log id         |
| Language   | string     | en  |

-   eg. `{"BetLogID":"6886d98ca0dacc95b4e3d16f","Language":"en"}` 


#### Return Result:

| Parameter Name  | Type       | Description                                           |
|------------|----------|-----------------------|
| Url         | string   | display url |


- eg. ```https://GAMEDETAILDOMAIN/#/history?gid=gameid_10010&l=en&betid=6886d98ca0dacc95b4e3d16e&time=1753856027```

## APIs Provided by the Operator

- OPURL is the http access address provided by your side, we will request player data through http POST

- All requests and responses use Json encoding

- Please first check that the AppID and AppSecret fields match your side

- If your side has an error, please set *code* to a non-zero value and *error* to a non-empty string

- e.g. OPURL=https://balabala.com/xxxx or http://10.0.0.1:8888


Note: The Api on the carrier side can also be set to encrypt transmission, and the encryption method can be set with our api. Whether it is encrypted can be set by contacting our staff (no encryption by default).
Also: *** For security reasons, it is strongly recommended to enable encryption and set up our ip whitelist access.***

### Get Player Balance

> URL: OPURL/Cash/Get

#### Request Parameters:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| AppID | string | Operator's unique identifier |
| AppSecret | string | Operator's AppSecret |
| UserID | string | Operator's unique player identifier |
| GameID    string | game id, ***Currently, only the staging environment is available***   |

- e.g.
```{"UserID":"testuser1","AppID":"fake","AppSecret":"1234-abcd-xxxkkk"}```

#### Return Result:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| Balance | float | Player's current available balance |

- e.g. ```{"code":0,"error":"","data":{"Balance":10994.12}}```

### Modify Player Balance

> URL: OPURL/Cash/TransferInOut

#### Request Parameters:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| AppID | string | Operator's unique identifier |
| AppSecret | string | Operator's AppSecret |
| UserID | string | Operator's unique player identifier |
| TransactionID | string | Transaction order number |
| Amount | float | Amount to add/deduct (+ add, - deduct) |
| RoundID | string | RoundID |
| GameID | string | GameID |
| ReqTime | datetime | Request time |
| Reason | string | See below for detailed explanation |
| iscompleted | bool | Identifies the last operation balance of this round in the slots game (true: last) |



- Possible Reason values:
    - "win" bonus
    - "bet" bet deduction
    - "frbbet" free spin bet, obtained by the operator creating a free game
    - "refund" internal server error, refund bet
    - "pokerin" some poker games withhold when entering the room
    - "pokerout" some poker games refund when leaving the room
    - "cancelBet" some poker games refund when settlement

- If we encounter a Timeout error when requesting this interface, we may continuously request several times in the following short period. Please use TransactionID to judge duplicates to ensure idempotent

- If a player bets once, multiple eliminations may occur, and the operator will receive **1** balance deduction requests, and **n (n>=1)** prize distribution requests. If there is no prize distribution for this elimination , Amount is **0**, and the interface still needs to return the player's current balance. The above requests to operate the balance are regarded as the same Round, with the same RoundID
 
- NOTICE ***If the balance is insufficient when deducting, an error needs to be returned.***

- e.g.
    ```{"UserID":"testuser1","AppID":"fake","AppSecret":"fake","TransactionID":"667f70ecc2a54a93fc4c2695","Amount":-1,"RoundID":"667f70ec68f376bf8c282a55","GameID":"gameid39","ReqTime":"2024-06-29T09:26:52.035906101+07:00","Reason":"bet"}```

STEP-BY-STEP: 
    Let's look at a simulated scenario that doesn't take into account external manipulation of the player's balance.

1. get balance 
    req.url: OPURL/Cash/Get    
    req.payload: {"UserID":"testuser1","GameID":"gameid39"}
    response: {"code":0,"error":"","data":{"Balance":100}}
    desc: 
    > current balance is 100.00

2. deduct, use TransId transId-test111
    req.url: OPURL/Cash/TransferInOut    
    req.payload: {"TransId":"transId-test111","GameID":"gameid39","UserID":"testuser1","Amount":-1,"RoundID":"roundid-test","Reason":"bet","IsCompleted":false}
    response: {"code":0,"error":"","data":{"Balance":99}}
    desc: 
    > After deducts the 1.0 balance, returns the new balance of 99.00
    
3. Deduct and repeat the order number transId-test111. Note that the order number is repeated in this case
    req.url: OPURL/Cash/TransferInOut    
    req.payload: {"TransId":"transId-test111","GameID":"gameid39","UserID":"testuser1","Amount":-1,"RoundID":"roundid-test","Reason":"bet","IsCompleted":false}
    response: {"code":0,"error":"","data":{"Balance":99}}
    desc: 
    > Duplicate order numbers are used only if the order status cannot be determined due to a previous operation timeout, etc
    > **Duplicate orders require operator judgment**.
        case1 Order number not processed: modify normally, return new balance.
        case2 If it has already been processed, the balance has been modified: Instead of adding or subtracting the balance repeatedly, the new balance is returned or the result of the last processing has been recorded.
        case3 If it has already been processed, but fails: Don't add or subdivide the balance repeatedly, return the logged error from the last processing.

4. To deduct excessive balances, use order number transId-test222
    req.url: OPURL/Cash/TransferInOut    
    req.payload: {"TransId":"transId-test222","GameID":"gameid39","UserID":"testuser1","Amount":-200,"RoundID":"roundid-test2","Reason":"bet","IsCompleted":false}
    response: {"code":1,"error":"Insufficient balance balabala...","data":null}
    desc: 
    > Return an error when deducting an excessive amount
    
5. To deduct excessive balances, use repeated order number transId-test222
    req.url: OPURL/Cash/TransferInOut    
    req.payload: {"TransId":"transId-test222","GameID":"gameid39","UserID":"testuser1","Amount":-200,"RoundID":"roundid-test2","Reason":"bet","IsCompleted":false}
    response: {"code":1,"error":"Insufficient balance balabala...","data":null}
    desc: 
    > The operator determines that the order has been processed and simply returns the last processed result.. {"code":1,"error":"Insufficient balance balabala...","data":null}
    
6. Deduct use repeated order number transId-test111, But the other fields of the request parameters are inconsistent. Note that this is unlikely to happen. If it does, please contact support to report the BUG
    req.url: OPURL/Cash/TransferInOut    
    req.payload: {"TransId":"transId-test111","GameID":"gameid_3diff","UserID":"testuser1diff","Amount":-1.12,"RoundID":"roundid-testdiff","Reason":"bet","IsCompleted":false}
    response: {"code":1,"error":"found repeated TransId balabala..."}
    desc: 
    > Find duplicate order numbers with mismatched parameters, return errors, and contact customer service.
    

#### Return Result:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| Balance | float | Player's current available balance after modification |

- e.g. ```{"code":0,"error":"","data":{"Balance":11004}}```
- e.g. ```{"code":1,"error":"some error occurred!!"}```

### actively pushes betting logs (optional)

- If you need this interface data, please contact us to apply

> URL: OPURL/History/Spin

#### Request Parameters:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |
| ID | string | Unique ID of the record in ascending order |
| Pid | int64 | Platform Player ID |
| UserID | string | Operator Player ID |
| GameID | string | GameID |
| Bet | float | Total bet amount |
| Win | float | Total win amount |
| WinLose | float | Net win/loss |
| InsertTime | datetime | Record the time of storage |
| AppID | string | Operator ID |
| Balance | float | Balance after settlement |
| Grade | int | Betting grade place |
| RoundID | string | Round ID, this field is the same for the same game |
| GameType   |  int     | 0: Slots,  3: Poker,  4: Lottery  |

- eg. ```{"AppID":"fake","Balance":10001,"Bet":1,"GameID":"gameid39","Grade":1001,"ID":"667f71df68f376bf8c282a57","InsertTime":"2024-06-29T09:30:55.229472064+07:00","Pid":100794,"RoundID":"667f71df68f376bf8c282a57","UserID":"testuser1","Win":0,"WinLose":-1, "GameType": 0}```

#### Return Result:

| Parameter Name | Type | Description |
| :--- | :--: | :---: |

- eg. ```{"code":0,"error":""}```