├── COPYING ├── EOS_pay.jpg ├── README.md ├── README_CN.md ├── XLM_pay.jpg ├── example.sh └── mixin_snap.go /COPYING: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 lilin # mixin.one 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /EOS_pay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myrual/mixin-network-snapshot-golang/4d1dfc2eaea0a1cb689277c9b6990a81e0fd80dc/EOS_pay.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
4 | 5 | # Cryptocurrency payment plugin(No maintainer NOW, No response if you ask question) 6 | 7 | Accept cryptocurrency payment can be painless, free and secure. 8 | * No need to setup full Bitcoin/Ethereum/EOS full node(it cost your hundreds gigabytes). 9 | * No need to pay expensive service fee, your program, your money. 10 | * All payment is automatically transfer to your personal account on the fly. No money to lose even database is stolen. 11 | 12 | Developer call to localhost http api, show payment information to client, program will visit webhook when client paid cryptocurrency. 13 | 14 | ATTENTION: If you ever use code on or before tag v0.0.1, the current master branch is not backward compatible. 15 | 16 | Steps: 17 | ### 1. Create a Mixin Messenger account. 18 | Visit https://mixin.one/messenger to download App from AppStore, Google Play. 19 | 20 | ### 2. Active developer account and create an app 21 | Log in to https://developer.mixin.one with your mixin messenger account 22 | 23 | This [tutorial](https://mixin-network.gitbook.io/mixin-network/mixin-messenger-app/create-bot-account) is very useful for new developer to create app. 24 | 25 | ### Clone, build, run 26 | ```shell 27 | git clone https://github.com/myrual/mixin-network-snapshot-golang 28 | cd mixin-network-snapshot-golang 29 | ``` 30 | 31 | 2. Edit some of code 32 | ```go 33 | const ( 34 | userid = "3c5fd587-5ac3-4fb6-b294-423ba3473f7d" 35 | sessionid = "42848ded-0ffd-45eb-9b46-094d5542ee01" 36 | private_key = `-----BEGIN RSA PRIVATE KEY----- 37 | MIICXAIBAAKBgQDACTrT4uaB9el9qe0MUOsFrm8kpaDI9PowauMB1Ha25mpfL+5h 38 | MFqISLS5z2P89nAsXBg+KyQ2gAVA6rBwW/ZqOc1PKiJhhLBS80nzo3ayfv7OlzNG 39 | IxMyqD5izCCtPixnqpuTePoPWq4CNZlxop0VYklsWEfU0U0qqMBgmtqYfQIDAQAB 40 | AoGAR8crZed5oTn5fC73m5LjRcxdXqVJ49MtcMuC7jwr41FckRepUkpwjGAgrRMH 41 | nJXAd9Q0e4hEkNppHEqciGLXR1dQfZnaM1Gnv7mD3oSgHaH+4qAMnNOCpvwW4Eu3 42 | yp9b1UGj9SvM3D2BrpA+MGf0E/yEJzpRcT956W6SPYYSegECQQDm4uTK+teoxr1Z 43 | agJZuCta+IhMzpxIWMob+JN/Huf7OnRcIa9JpXngg4tHOUWmZCDQdqeJMpaQc8SQ 44 | 44hba015AkEA1OyJswNIhdmvVp5P1zgREVVRK6JloYwmAtj+Qo4pWJ117LqH4w+b 45 | 491r4AeLEGh8VrZ4k6Hp+Cm783S2jTAWJQJARbWdlHdV45xVkQiDuyjy1h2RsXb0 46 | EpfUNcvAZLIlImIMvcBh1x+CA7pTs+Zj1BAJJEee37qJYQXDBGfeRJPKKQJAVG+c 47 | x42Ew/eoTZwoIzvLoOkJcFlNHjwaksSER9ZiVQ7URdVOr99vvXQAJG45Wn9k12oy 48 | 9LCfvNan/wqIngK0tQJBAL1Wc02seEbMeWyt5jycJEhn6G8F18s9S1v0GXb4U/7/ 49 | 6Y87P3TmDLcEuCXkrbZQaCX7jVLu0BkDw8To58TWjh0= 50 | -----END RSA PRIVATE KEY-----` 51 | ADMIN_MessengerID = ""//this is your mixin messenger id, you can find your id in contact page. 52 | ) 53 | ``` 54 | 55 | 3. Build 56 | ```shell 57 | go build mixin_snap.go 58 | ``` 59 | 4. Run 60 | ```shell 61 | ./mixin_snap 62 | ``` 63 | 64 | 5. Database 65 | 66 | A sqlite3 file with name test.db will be generated in same folder. 67 | 68 | 69 | ## How to 70 | 71 | #### Query current cryptocurrency price, so developer know how many asset client need to transfer. 72 | ```shell 73 | curl -X GET 'http://localhost:8080/assetsprice' 74 | ``` 75 | Result is following. 76 | ```json 77 | [ 78 | {"Fullname":"Stellar","Symbol":"XLM","USDPrice":0.10357796,"BTCPrice":0.00000889,"Assetid":"56e63c06-b506-4ec5-885a-4a5ac17b83c1"}, 79 | {"Fullname":"EOS","Symbol":"EOS","USDPrice":5.96024263,"BTCPrice":0.00051165,"Assetid":"6cfe566e-4aad-470b-8c9a-2fd35b49c68d"}, 80 | {"Fullname":"Ether","Symbol":"ETH","USDPrice":294.61322131,"BTCPrice":0.02529107,"Assetid":"43d61dcd-e413-450d-80b8-101d5e903357"} 81 | ] 82 | ``` 83 | 84 | If your order is valued about 1 USD, that means client need to deposit about 10 XLM, or 0.17 EOS. 85 | 86 | #### Create charge 87 | To accept bitcoin or eos payment, developer need to call localhost:8080/charges by http POST, with parameter in body. 88 | 89 | POST /charges 90 | 91 | |Attributes| type | description| 92 | |--|--|--| 93 | |currency| String | Currency code associated with the amount. Only EOS/XLM/ETH is supported currently| 94 | |amount| Float64 | Positive float| 95 | |customerid| String | This field is optional and can be used to attach an identifier of your choice to the charge. Must not exceed 64 characters| 96 | |webhookurl| String | program will visit localhost+webhook when user pay enough currency before charge is expired | 97 | |expiredafter| uint | the webhook will be expired after xx minutes. User can pay to an expired charge , program keep income record and will transfer asset to admin account| 98 | 99 | 100 | Example: let client "client1245" pay 0.001 ETH, notify developer's app by POST localhost:9090/123 when user pay enough currency in 60 minutes. 101 | ```shell 102 | curl -d '{"currency":"ETH", "amount":0.001, "customerid":"client1245", "webhookurl":":9090/123", "expiredafter":60}' -H "Content-Type: application/json" 127.0.0.1:8080/charges 103 | ``` 104 | The command just tell the program to create a ETH charge address for customer id "client1245", visit localhost:9090/123 when user paid enough asset to the address in 60 minutes. 105 | 106 | the result of the command will be 107 | ```json 108 | { 109 | "Id":3, 110 | "Currency":"ETH", 111 | "Amount":0.001, 112 | "Customerid":"client1245", 113 | "Webhookurl":":9090/123", 114 | "Expired_after":60, 115 | "Paymentmethod":{ 116 | "Name":"ETH", 117 | "PaymentAddress":"0x130D3e6655f073e33235e567E7A1e1E1f59ddD79", 118 | "PaymentAccount":"", 119 | "PaymentMemo":"", 120 | "Priceinusd":"310.40105841", 121 | "Priceinbtc":"0.02374051" 122 | }, 123 | "Receivedamount":0, 124 | "Paidstatus":0} 125 | ``` 126 | Client need to tranfser 0.001 ETH to address 0x130D3e6655f073e33235e567E7A1e1E1f59ddD79 to finish the payment. 127 | 128 | If you want to accept EOS 129 | 130 | ```shell 131 | $ curl -d '{"currency":"EOS", "amount":0.001, "customerid":"client1245", "webhookurl":":9090/123", "expiredafter":5}' -H "Content-Type: application/json" 127.0.0.1:8080/charges 132 | ``` 133 | ```json 134 | { 135 | "Id":2, 136 | "Currency":"EOS", 137 | "Amount":0.001, 138 | "Customerid":"client1245", 139 | "Webhookurl":":9090/123", 140 | "Expired_after":5, 141 | "Paymentmethod":{ 142 | "Name":"EOS", 143 | "PaymentAddress":"", 144 | "PaymentAccount":"eoswithmixin", 145 | "PaymentMemo":"a01a148f234ea8be0229a4422d21e7f3", 146 | "Priceinusd":"4.63264861", 147 | "Priceinbtc":"0.00040277" 148 | }, 149 | "Receivedamount":0, 150 | "Paidstatus":0 151 | } 152 | ``` 153 | Client need to tranfser 0.001 EOS to account eoswithmixin, and MUST fill memo a01a148f234ea8be0229a4422d21e7f3 to finish the payment. 154 |  155 | 156 | If you want to accept Stellar XLM 157 | ```shell 158 | curl -d '{"currency":"XLM", "amount":0.001, "customerid":"client1245", "webhookurl":":9090/123", "expiredafter":5}' -H "Content-Type: application/json" 127.0.0.1:8080/charges 159 | ``` 160 | ```json 161 | { 162 | "Id":3, 163 | "Currency":"XLM", 164 | "Amount":0.001, 165 | "Customerid":"client1245", 166 | "Webhookurl":":9090/123", 167 | "Expired_after":5, 168 | "Paymentmethod":{ 169 | "Name":"XLM", 170 | "PaymentAddress":"", 171 | "PaymentAccount":"GD77JOIFC622O5HXU446VIKGR5A5HMSTAUKO2FSN5CIVWPHXDBGIAG7Y", 172 | "PaymentMemo":"45da67ad857c907a", 173 | "Priceinusd":"0.08866487", 174 | "Priceinbtc":"0.00000769" 175 | }, 176 | "Receivedamount":0, 177 | "Paidstatus":0 178 | } 179 | ``` 180 | Client need to tranfser 0.001 XLM to account GD77JOIFC622O5HXU446VIKGR5A5HMSTAUKO2FSN5CIVWPHXDBGIAG7Y, and MUST fill memo 45da67ad857c907a to finish the payment. 181 |  182 | 183 | There are two types of payment method: 184 | 1. Bitcoin/Ethereum style: PaymentAddress is not empty, PaymentAccount and PaymentMemo are all empty. You just show Ethererum Name and PaymentAddress to your clients, they just need to transfer token to the address. In this example, show asset name ETH, payment address 0x365DA43BC7B22CD4334c3f35eD189C8357D4bEd6 and payment amount to your client. 185 | 2. EOS/Stellar style: PaymentAddress is empty, PaymentAccount and PaymentMemo are not empty. You need to show Asset Name and both of PaymentAccount and PaymentMemo to user, and remind user need to input BOTH of PaymentAccount and PaymentMemo. Transfer asset to PaymentAccount without memo is a common mistake, and it can not be reverted because current Mixin Network limitation. In this example, show asset name EOS, payment account eoswithmixin , payment memo 302c37ebff05ccf09dd7296053d1924a. 186 | 187 | Asset current price in USD and Bitcoin is inside payment record, so developer can calculate how many asset client should transfer to the address or account. 188 | 189 | ```json 190 | { 191 | "Priceinusd":"310.40105841", 192 | "Priceinbtc":"0.02374051" 193 | } 194 | ``` 195 | 196 | Currency list 197 | 198 | |Currency| Explain | introduction| 199 | |-| - | - | 200 | |EOS|EOS.io main chain token|-| 201 | |XLM|Stellar main chain token|-| 202 | |BTC|Bitcoin|-| 203 | |UDT|Tether USD|Running on Bitcoin instead of Ethereum| 204 | |XRP|Ripple|-| 205 | |LTC|Litecoin|-| 206 | 207 | #### Query payment status 208 | fetch the payment status by visit localhost:8080/charges with parameter charge_id 209 | 210 | Example: 211 | ```shell 212 | curl -X GET 'http://localhost:8080/charges?charge_id=3' 213 | 214 | ``` 215 | 216 | Response will be similar to following if payment is not yet confirmed 217 | ```json 218 | { 219 | "Id":3, 220 | "Currency":"ETH", 221 | "Amount":0.001, 222 | "Customerid":"client1245", 223 | "Webhookurl":":9090/123", 224 | "Expired_after":60, 225 | "Paymentmethod":{ 226 | "Name":"ETH", 227 | "PaymentAddress":"0x130D3e6655f073e33235e567E7A1e1E1f59ddD79", 228 | "PaymentAccount":"", 229 | "PaymentMemo":"", 230 | "Priceinusd":"310.40105841", 231 | "Priceinbtc":"0.02374051" 232 | }, 233 | "Receivedamount":0, 234 | "Paidstatus":0} 235 | } 236 | ``` 237 | 238 | Response will be similar to following if payment is already confirmed 239 | ```json 240 | { 241 | "Id":3, 242 | "Currency":"ETH", 243 | "Amount":0.001, 244 | "Customerid":"client1245", 245 | "Webhookurl":":9090/123", 246 | "Expired_after":60, 247 | "Paymentmethod":{ 248 | "Name":"ETH", 249 | "PaymentAddress":"0x130D3e6655f073e33235e567E7A1e1E1f59ddD79", 250 | "PaymentAccount":"", 251 | "PaymentMemo":"", 252 | "Priceinusd":"309.75108846", 253 | "Priceinbtc":"0.02369282" 254 | }, 255 | "Receivedamount":0.002, 256 | "Paidstatus":2 257 | } 258 | ``` 259 | 260 | Paid status 261 | 262 | |value | description| 263 | |--|--| 264 | |0| not yet paid| 265 | |1| partial paid| 266 | |2| paid| 267 | |3| over paid| 268 | 269 | The payment address is a deposit address in cryptocurrency world, so user can deposit any amount. 270 | 271 | 272 | #### payment notification webhook 273 | The program will visit webhook url when user paid and confirmed by program. 274 | ```json 275 | "http://127.0.0.1"+webhookurl 276 | ``` 277 | The http visit method is POST, json body parameter is following 278 | ```json 279 | { 280 | "Id":3, 281 | "Currency":"ETH", 282 | "Amount":0.001, 283 | "Customerid":"client1245", 284 | "Webhookurl":":9090/123", 285 | "Expired_after":60, 286 | "Paymentmethod":{ 287 | "Name":"ETH", 288 | "PaymentAddress":"0x130D3e6655f073e33235e567E7A1e1E1f59ddD79", 289 | "PaymentAccount":"", 290 | "PaymentMemo":"", 291 | "Priceinusd":"309.75108846", 292 | "Priceinbtc":"0.02369282" 293 | }, 294 | "Receivedamount":0.0021, 295 | "Paidstatus":2 296 | } 297 | ``` 298 | Developer can know when, which asset is paid by client, and what's the payment value in USD and Bitcoin. 299 | 300 | 301 | ### How did developer get all asset? 302 | 1. All income payment will be AUTOMATICALLY sent to your own Mixin Messenger account with ZERO transaction fee in 1 seconds. 303 | 2. You can also ask the program send all money to your Mixin Messenger account if the program exit accidently. 304 | 305 | ```shell 306 | curl -X POST -H "Content-Type: application/json" 127.0.0.1:8080/moneygohome 307 | ``` 308 | 309 | response will be similar to follow 310 | ```json 311 | total 20 account will send all balance to admin 312 | ``` 313 | 314 | ### payment confirmation time 315 | 1. EOS: 3 minutes 316 | 2. Stellar: 2 minutes 317 | 3. Bitcoin/USDT: 60 minutes 318 | 4. Litecoin/Ethererum/DOGE: 120 minutes 319 | 320 | #### What is confirmation time, why need to care about it? 321 | A cryptocurrency transaction created by your client need to be confirmed by network, Bitcoin network need long time to confirm, other blockchain need less time. 322 | 323 | ### What kind of currency can be supported 324 | All asset supported by Mixin Network: 325 | BTC, USDT, BCH, ETH and ERC20, ETC, EOS and token issue on EOS, DASH, Litecoin, Doge, Horizen, MGD, NEM, XRP, XLM, TRON and TRC10, Zcash. 326 | 327 | ### Recommend Currency 328 | Three kind of currency : ETH, EOS, XLM are accepted in code. 329 | 330 | To support more currency, just add more asset into the default_asset_id_group. 331 | ```go 332 | const ( 333 | BTC_ASSET_ID = "c6d0c728-2624-429b-8e0d-d9d19b6592fa" 334 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d" 335 | USDT_ASSET_ID = "815b0b1a-2764-3736-8faa-42d694fa620a" 336 | ETC_ASSET_ID = "2204c1ee-0ea2-4add-bb9a-b3719cfff93a" 337 | XRP_ASSET_ID = "23dfb5a5-5d7b-48b6-905f-3970e3176e27" 338 | XEM_ASSET_ID = "27921032-f73e-434e-955f-43d55672ee31" 339 | ETH_ASSET_ID = "43d61dcd-e413-450d-80b8-101d5e903357" 340 | DASH_ASSET_ID = "6472e7e3-75fd-48b6-b1dc-28d294ee1476" 341 | DOGE_ASSET_ID = "6770a1e5-6086-44d5-b60f-545f9d9e8ffd" 342 | LTC_ASSET_ID = "76c802a2-7c88-447f-a93e-c29c9e5dd9c8" 343 | SIA_ASSET_ID = "990c4c29-57e9-48f6-9819-7d986ea44985" 344 | ZEN_ASSET_ID = "a2c5d22b-62a2-4c13-b3f0-013290dbac60" 345 | ZEC_ASSET_ID = "c996abc9-d94e-4494-b1cf-2a3fd3ac5714" 346 | BCH_ASSET_ID = "fd11b6e3-0b87-41f1-a41f-f0e9b49e5bf0" 347 | XIN_ASSET_ID = "c94ac88f-4671-3976-b60a-09064f1811e8" 348 | CNB_ASSET_ID = "965e5c6e-434c-3fa9-b780-c50f43cd955c" 349 | XLM_ASSET_ID = "56e63c06-b506-4ec5-885a-4a5ac17b83c1" 350 | TRON_ASSET_ID = "25dabac5-056a-48ff-b9f9-f67395dc407c" 351 | ........ 352 | ) 353 | ....... 354 | ....... 355 | 356 | // to support more asset, just add them in the following array 357 | default_asset_id_group := []string{XLM_ASSET_ID, EOS_ASSET_ID, ETH_ASSET_ID} 358 | ``` 359 | 360 | 361 | TO BE DONE: 362 | 1. All asset can be withdrawed to developer's cold wallet. 363 | 2. One type asset can be exchanged to USDT or Bitcoin automatically through DEX. 364 | 3. Support Mixin Messenger User to pay. 365 | 4. ~~Latest USD price for every asset~~ Implemented in commit 8a634e23254e4841c2a9c3114b3eb847d46f55fc 366 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # 数字货币收款插件(不维护了,没空回答问题) 2 | 轻松,免费,安全的数字货币收款方案 3 | * 无需搭建比特币/以太坊/EOS全节点(每一个都需要几百G空间) 4 | * 无手续费,你的程序你做主 5 | * 所有收到的钱实时自动转移到开发者个人账户,即使被拖库也没钱可盗。 6 | 7 | 开发者访问本地 http 接口,向用户展示付款方法,用户付款后程序会访问本地回调URL 8 | 9 | 步骤: 10 | ### 1. 创建一个Mixin Messenger账户. 11 | 访问 https://mixin.one/messenger 下载对应手机端App。 12 | 13 | 中国大陆用户可以访问 https://a.app.qq.com/o/simple.jsp?pkgname=one.mixin.messenger 下载 14 | 15 | ### 2. 激活开发者账号 16 | 登陆 https://developer.mixin.one ,用App扫码登录 17 | 18 | 这个 [教程](https://mixin-network.gitbook.io/mixin-network/mixin-messenger-app/create-bot-account)对于新开发者很有用。 19 | 20 | ### Clone, build, run 21 | ```shell 22 | git clone https://github.com/myrual/mixin-network-snapshot-golang 23 | cd mixin-network-snapshot-golang 24 | ``` 25 | 26 | 2. 编辑一部分配置信息 27 | ```go 28 | const ( 29 | userid = "3c5fd587-5ac3-4fb6-b294-423ba3473f7d" 30 | sessionid = "42848ded-0ffd-45eb-9b46-094d5542ee01" 31 | private_key = `-----BEGIN RSA PRIVATE KEY----- 32 | MIICXAIBAAKBgQDACTrT4uaB9el9qe0MUOsFrm8kpaDI9PowauMB1Ha25mpfL+5h 33 | MFqISLS5z2P89nAsXBg+KyQ2gAVA6rBwW/ZqOc1PKiJhhLBS80nzo3ayfv7OlzNG 34 | IxMyqD5izCCtPixnqpuTePoPWq4CNZlxop0VYklsWEfU0U0qqMBgmtqYfQIDAQAB 35 | AoGAR8crZed5oTn5fC73m5LjRcxdXqVJ49MtcMuC7jwr41FckRepUkpwjGAgrRMH 36 | nJXAd9Q0e4hEkNppHEqciGLXR1dQfZnaM1Gnv7mD3oSgHaH+4qAMnNOCpvwW4Eu3 37 | yp9b1UGj9SvM3D2BrpA+MGf0E/yEJzpRcT956W6SPYYSegECQQDm4uTK+teoxr1Z 38 | agJZuCta+IhMzpxIWMob+JN/Huf7OnRcIa9JpXngg4tHOUWmZCDQdqeJMpaQc8SQ 39 | 44hba015AkEA1OyJswNIhdmvVp5P1zgREVVRK6JloYwmAtj+Qo4pWJ117LqH4w+b 40 | 491r4AeLEGh8VrZ4k6Hp+Cm783S2jTAWJQJARbWdlHdV45xVkQiDuyjy1h2RsXb0 41 | EpfUNcvAZLIlImIMvcBh1x+CA7pTs+Zj1BAJJEee37qJYQXDBGfeRJPKKQJAVG+c 42 | x42Ew/eoTZwoIzvLoOkJcFlNHjwaksSER9ZiVQ7URdVOr99vvXQAJG45Wn9k12oy 43 | 9LCfvNan/wqIngK0tQJBAL1Wc02seEbMeWyt5jycJEhn6G8F18s9S1v0GXb4U/7/ 44 | 6Y87P3TmDLcEuCXkrbZQaCX7jVLu0BkDw8To58TWjh0= 45 | -----END RSA PRIVATE KEY-----` 46 | ADMIN_MessengerID = ""//this is your mixin messenger id, you can find your id in contact page. 47 | ) 48 | ``` 49 | 3. 编译 50 | ```shell 51 | go build mixin_snap.go 52 | ``` 53 | 4. 运行 54 | ```shell 55 | ./mixin_snap 56 | ``` 57 | 58 | 5. 数据库 59 | 同一目录下会生成一个test.db 的sqlite3文件。 60 | 61 | 62 | ## 如何使用 63 | #### 获取数字资产当前价格信息,因此可以计算客户应该付多少数字资产 64 | ```shell 65 | curl -X GET 'http://localhost:8080/assetsprice' 66 | ``` 67 | 68 | 价格结果如下,其中Full Name是该币种全名,Symbol是在交易所和钱包的缩写符号,USDPrice是当前资产美元价格,BTCPrice同理。 69 | ```json 70 | [ 71 | {"Fullname":"Stellar","Symbol":"XLM","USDPrice":0.10357796,"BTCPrice":0.00000889,"Assetid":"56e63c06-b506-4ec5-885a-4a5ac17b83c1"}, 72 | {"Fullname":"EOS","Symbol":"EOS","USDPrice":5.96024263,"BTCPrice":0.00051165,"Assetid":"6cfe566e-4aad-470b-8c9a-2fd35b49c68d"}, 73 | {"Fullname":"Ether","Symbol":"ETH","USDPrice":294.61322131,"BTCPrice":0.02529107,"Assetid":"43d61dcd-e413-450d-80b8-101d5e903357"} 74 | ] 75 | ``` 76 | 如果订单价值1美金,那么根据资产价格可以知道客户需要 10 XLM, 或者 0.17 EOS。 77 | #### 创建支付请求 78 | 用POST方法访问 localhost:8080/charges,参数如下 79 | 80 | POST /charges 81 | 82 | |Attributes| type | description| 83 | |--|--|--| 84 | |currency| String | Currency code associated with the amount. Only EOS/XLM/ETH is supported currently| 85 | |amount| Float64 | Positive float| 86 | |customerid| String | This field is optional and can be used to attach an identifier of your choice to the charge. Must not exceed 64 characters| 87 | |webhookurl| String | program will visit localhost+webhook when user pay enough currency before charge is expired | 88 | |expiredafter| uint | the webhook will be expired after xx minutes. User can pay to an expired charge , program keep income record and will transfer asset to admin account| 89 | 90 | 举例: 需要让客户 "client1245" 支付 0.001 ETH, 60分钟内支付完成之后访问用 POST 访问 localhost:9090/123。 91 | ```shell 92 | curl -d '{"currency":"ETH", "amount":0.001, "customerid":"client1245", "webhookurl":":9090/123", "expiredafter":60}' -H "Content-Type: application/json" 127.0.0.1:8080/charges 93 | ``` 94 | 95 | 这条指令返回结果 96 | ```json 97 | { 98 | "Id":3, 99 | "Currency":"ETH", 100 | "Amount":0.001, 101 | "Customerid":"client1245", 102 | "Webhookurl":":9090/123", 103 | "Expired_after":60, 104 | "Paymentmethod":{ 105 | "Name":"ETH", 106 | "PaymentAddress":"0x130D3e6655f073e33235e567E7A1e1E1f59ddD79", 107 | "PaymentAccount":"", 108 | "PaymentMemo":"", 109 | "Priceinusd":"310.40105841", 110 | "Priceinbtc":"0.02374051" 111 | }, 112 | "Receivedamount":0, 113 | "Paidstatus":0} 114 | ``` 115 | 客户需要向以太坊地址 0x130D3e6655f073e33235e567E7A1e1E1f59ddD79 支付0.001 ETH来完成支付。 116 | 117 | 如果想收EOS 118 | ```shell 119 | $ curl -d '{"currency":"EOS", "amount":0.001, "customerid":"client1245", "webhookurl":":9090/123", "expiredafter":5}' -H "Content-Type: application/json" 127.0.0.1:8080/charges 120 | ``` 121 | ```json 122 | { 123 | "Id":2, 124 | "Currency":"EOS", 125 | "Amount":0.001, 126 | "Customerid":"client1245", 127 | "Webhookurl":":9090/123", 128 | "Expired_after":5, 129 | "Paymentmethod":{ 130 | "Name":"EOS", 131 | "PaymentAddress":"", 132 | "PaymentAccount":"eoswithmixin", 133 | "PaymentMemo":"a01a148f234ea8be0229a4422d21e7f3", 134 | "Priceinusd":"4.63264861", 135 | "Priceinbtc":"0.00040277" 136 | }, 137 | "Receivedamount":0, 138 | "Paidstatus":0 139 | } 140 | ``` 141 | 客户需要向EOS账户 eoswithmixin 支付 0.001 EOS, 并且必须填写支付备注 a01a148f234ea8be0229a4422d21e7f3。 142 |  143 | 144 | 如果想收XLM 145 | ```shell 146 | curl -d '{"currency":"XLM", "amount":0.001, "customerid":"client1245", "webhookurl":":9090/123", "expiredafter":5}' -H "Content-Type: application/json" 127.0.0.1:8080/charges 147 | ``` 148 | ```json 149 | { 150 | "Id":3, 151 | "Currency":"XLM", 152 | "Amount":0.001, 153 | "Customerid":"client1245", 154 | "Webhookurl":":9090/123", 155 | "Expired_after":5, 156 | "Paymentmethod":{ 157 | "Name":"XLM", 158 | "PaymentAddress":"", 159 | "PaymentAccount":"GD77JOIFC622O5HXU446VIKGR5A5HMSTAUKO2FSN5CIVWPHXDBGIAG7Y", 160 | "PaymentMemo":"45da67ad857c907a", 161 | "Priceinusd":"0.08866487", 162 | "Priceinbtc":"0.00000769" 163 | }, 164 | "Receivedamount":0, 165 | "Paidstatus":0 166 | } 167 | ``` 168 | 客户需要向Stellar账户 GD77JOIFC622O5HXU446VIKGR5A5HMSTAUKO2FSN5CIVWPHXDBGIAG7Y 支付 0.001 XLM, 并且必须填写支付备注 45da67ad857c907a 169 |  170 | 171 | 172 | Payment_method里面有两种类型的支付: 173 | 1. 比特币/以太坊: PaymentAddress 不是空,PaymentAccount 和 PaymentMemo是空。这种情况下,你只需要给用户展示资产名字 以太坊和PaymentAddress,客户只需要向以太坊地址付款。在这个例子里面,向用户展示资产名称 ETH,以及收款地址 0x365DA43BC7B22CD4334c3f35eD189C8357D4bEd6,以及你期望的以太坊数量。 174 | 2. EOS/行星 : PaymentAddress 是空, PaymentAccount 和 PaymentMemo 都有内容。这种情况下,你需要给用户展示资产名字,收款账户和收款备注,并且严肃的提醒用户同时填写收款账户和收款备注,客户如果忘记填写备注,会导致不能到账,而且无法退款。 175 | 176 | Payment_method的记录内容里面有该资产当前的美元价格和比特币价格,开发者可以根据订单的美元价格来计算客户应该支付多少数字货币。 177 | ```json 178 | {"Priceinusd":"0.10472789","Priceinbtc":"0.00000925"} 179 | ``` 180 | 181 | 支持的货币列表 182 | 183 | |Currency| 说明 | 介绍| 184 | |-| - | - | 185 | |EOS|EOS.io 主网token|-| 186 | |XLM|Stellar 主网token|-| 187 | |BTC|比特币|-| 188 | |UDT|Tether USD|基于比特币的USDT,不是ERC20的代币| 189 | |XRP|锐波币|-| 190 | |LTC|莱特币|-| 191 | 192 | #### 检查收款状态 193 | 访问 localhost:8080/charges, 带有参数charge_id 194 | 195 | 例子 196 | ```shell 197 | curl -X GET 'http://localhost:8080/charges?charge_id=3' 198 | 199 | ``` 200 | 201 | 如果客户还没有支付,结果如下 202 | ```json 203 | { 204 | "Id":3, 205 | "Currency":"ETH", 206 | "Amount":0.001, 207 | "Customerid":"client1245", 208 | "Webhookurl":":9090/123", 209 | "Expired_after":60, 210 | "Paymentmethod":{ 211 | "Name":"ETH", 212 | "PaymentAddress":"0x130D3e6655f073e33235e567E7A1e1E1f59ddD79", 213 | "PaymentAccount":"", 214 | "PaymentMemo":"", 215 | "Priceinusd":"310.40105841", 216 | "Priceinbtc":"0.02374051" 217 | }, 218 | "Receivedamount":0, 219 | "Paidstatus":0} 220 | } 221 | ``` 222 | 223 | 如果客户已经支付了,那么结果如下 224 | ```json 225 | { 226 | "Id":3, 227 | "Currency":"ETH", 228 | "Amount":0.001, 229 | "Customerid":"client1245", 230 | "Webhookurl":":9090/123", 231 | "Expired_after":60, 232 | "Paymentmethod":{ 233 | "Name":"ETH", 234 | "PaymentAddress":"0x130D3e6655f073e33235e567E7A1e1E1f59ddD79", 235 | "PaymentAccount":"", 236 | "PaymentMemo":"", 237 | "Priceinusd":"309.75108846", 238 | "Priceinbtc":"0.02369282" 239 | }, 240 | "Receivedamount":0.002, 241 | "Paidstatus":2 242 | } 243 | ``` 244 | 245 | 支付状态 Paidstatus的解释 246 | 247 | |值 | 解释| 248 | |--|--| 249 | |0| 还没有支付| 250 | |1| 支付不足| 251 | |2| 支付完毕| 252 | |3| 支付超过需求| 253 | 254 | #### 回调URL 255 | 用户支付完毕后,程序会访问本地+webook url 256 | ```json 257 | "http://127.0.0.1"+webhookurl 258 | ``` 259 | http 方法为POST,body 参数如下 260 | ```json 261 | { 262 | "Id":3, 263 | "Currency":"ETH", 264 | "Amount":0.001, 265 | "Customerid":"client1245", 266 | "Webhookurl":":9090/123", 267 | "Expired_after":60, 268 | "Paymentmethod":{ 269 | "Name":"ETH", 270 | "PaymentAddress":"0x130D3e6655f073e33235e567E7A1e1E1f59ddD79", 271 | "PaymentAccount":"", 272 | "PaymentMemo":"", 273 | "Priceinusd":"309.75108846", 274 | "Priceinbtc":"0.02369282" 275 | }, 276 | "Receivedamount":0.0021, 277 | "Paidstatus":2 278 | } 279 | ``` 280 | 281 | 282 | ### 所有资产都属于开发者自己么? 283 | 1. 所有资产都会自动被转移到你指定的账户, 免手续费,1秒到账。 284 | 2. 在该程序重启,或者意外退出之后,你可以手动发指令要求程序把所有资产都转移到你指定的账户。 285 | 286 | 例子: 287 | ```shell 288 | curl -X POST -H "Content-Type: application/json" 127.0.0.1:8080/moneygohome 289 | ``` 290 | 291 | 结果如下 292 | ```json 293 | total 20 account will send all balance to admin 294 | ``` 295 | 296 | ### 支付的确认时间 297 | 1. EOS: 3 分钟 298 | 2. Stellar: 2 分钟 299 | 3. Bitcoin/USDT: 60 分钟 300 | 4. Litecoin/Ethererum/DOGE: 120 分钟 301 | 302 | #### 什么是确认时间?为什么要关心确认时间? 303 | 数字货币从用户发起转账请求,到收款方确认这笔付款不能回滚需要一点时间,比特币需要的时间长,其他需要的时间短一点。 304 | 305 | ### 支持哪些资产 306 | 理论上Mixin Network支持的都可以接受。现在支持 307 | BTC, USDT, BCH, 以太坊和 ERC20, ETC, EOS 以及EOS上发的token, DASH, Litecoin, Doge, Horizen, MGD, NEM, XRP, XLM, 波场和波场上发的TRC10, Zcash. 308 | 309 | ### 目前的代码库默认支持的资产 310 | 现在代码里面默认支持的资产是EOS和恒星,因为他们都可以3分钟完成支付确认。 311 | 312 | 想要支持更多的币,把对应资产的变量放到 default_asset_id_group 里面就可以. 313 | ```go 314 | const ( 315 | BTC_ASSET_ID = "c6d0c728-2624-429b-8e0d-d9d19b6592fa" 316 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d" 317 | USDT_ASSET_ID = "815b0b1a-2764-3736-8faa-42d694fa620a" 318 | ETC_ASSET_ID = "2204c1ee-0ea2-4add-bb9a-b3719cfff93a" 319 | XRP_ASSET_ID = "23dfb5a5-5d7b-48b6-905f-3970e3176e27" 320 | XEM_ASSET_ID = "27921032-f73e-434e-955f-43d55672ee31" 321 | ETH_ASSET_ID = "43d61dcd-e413-450d-80b8-101d5e903357" 322 | DASH_ASSET_ID = "6472e7e3-75fd-48b6-b1dc-28d294ee1476" 323 | DOGE_ASSET_ID = "6770a1e5-6086-44d5-b60f-545f9d9e8ffd" 324 | LTC_ASSET_ID = "76c802a2-7c88-447f-a93e-c29c9e5dd9c8" 325 | SIA_ASSET_ID = "990c4c29-57e9-48f6-9819-7d986ea44985" 326 | ZEN_ASSET_ID = "a2c5d22b-62a2-4c13-b3f0-013290dbac60" 327 | ZEC_ASSET_ID = "c996abc9-d94e-4494-b1cf-2a3fd3ac5714" 328 | BCH_ASSET_ID = "fd11b6e3-0b87-41f1-a41f-f0e9b49e5bf0" 329 | XIN_ASSET_ID = "c94ac88f-4671-3976-b60a-09064f1811e8" 330 | CNB_ASSET_ID = "965e5c6e-434c-3fa9-b780-c50f43cd955c" 331 | XLM_ASSET_ID = "56e63c06-b506-4ec5-885a-4a5ac17b83c1" 332 | TRON_ASSET_ID = "25dabac5-056a-48ff-b9f9-f67395dc407c" 333 | ........ 334 | ) 335 | ....... 336 | ....... 337 | 338 | // to support more asset, just add them in the following array 339 | default_asset_id_group := []string{XLM_ASSET_ID, EOS_ASSET_ID} 340 | ``` 341 | 342 | 343 | 下一步的开发任务: 344 | 1. 所有的资产可以自动提取到开发者自己的冷钱包,而不是只能转移到Mixin Messenger账户。 345 | 2. 可以把收到的资产通过去中心化交易所自动转换成USDT或者比特币。 346 | 3. 支持Mixin Messenger用户付款。 347 | 4. ~~可以提供资产对应的美元价格~~ 在commit 8a634e23254e4841c2a9c3114b3eb847d46f55fc 中已经完成。 348 | -------------------------------------------------------------------------------- /XLM_pay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myrual/mixin-network-snapshot-golang/4d1dfc2eaea0a1cb689277c9b6990a81e0fd80dc/XLM_pay.jpg -------------------------------------------------------------------------------- /example.sh: -------------------------------------------------------------------------------- 1 | curl -X GET 'http://localhost:8080/payment?reqid=abcde' 2 | curl -d '{"reqid":"value8", "callback":":9090/"}' -H "Content-Type: application/json" 127.0.0.1:8080/payment 3 | 4 | curl -X POST -H "Content-Type: application/json" 127.0.0.1:8080/moneygohome 5 | -------------------------------------------------------------------------------- /mixin_snap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net/http" 11 | "strconv" 12 | "time" 13 | 14 | messenger "github.com/MooooonStar/mixin-sdk-go/messenger" 15 | mixin "github.com/MooooonStar/mixin-sdk-go/network" 16 | "github.com/gofrs/uuid" 17 | "github.com/jinzhu/gorm" 18 | _ "github.com/jinzhu/gorm/dialects/sqlite" 19 | ) 20 | 21 | const ( 22 | userid = "3c5fd587-5ac3-4fb6-b294-423ba3473f7d" 23 | sessionid = "82365995-4d86-4676-a632-4354f1a04954" 24 | private_key = `-----BEGIN RSA PRIVATE KEY----- 25 | MIICXgIBAAKBgQCIJJ+PfATaR3UDegG13fs0Rgx21+jpEB/aVOqrDNQUmuY5k/MC 26 | GRicNeIa+J2gT6lzPfWDL8z5IABnlc6EgI4BKM1ok0UgeQy/DWvDAmik47vEMpjU 27 | JjKwgwyarQA/yWl1R/9djt/cVOwxekOXJqNAjsfGzjArgUAzBZ3OYNr15wIDAQAB 28 | AoGAL5kmVCMXCz3KcmG4sV4f0qHe/7nzC3EAwfPIa+87Qsz5Sw4n+wbNLOhF2gos 29 | Cf1wEAOMj8YpkrwWiCC/KGJNwyGvCMWs166oA0KFPeMhpKMlqXE+DpNJQzit3mb1 30 | nZf18MXSMHw2u8LFfMDjJb2QZTOMVvWjYKnVg1AchfcPbeECQQDtW26N9lMTlzXO 31 | MxrtheenHuSEgm55wGRhKquxDpXbJNcalHAnIvfd5tGbuciMbVlEk01MF7ggEWID 32 | IhSHXhvRAkEAktYRV2XetVvF13orMGMPag9akdCjCpnA8Yxn11S0HSGsM6nkrMKl 33 | i7t+qDTjNUODsnOMzb34iJCNtaaHGL48NwJBAKiQ6Yfaaw+bsLObKcGL+oN+dg4B 34 | T5IZ52/2TO62jAiRNk6DIs84j03BUhVFML9CHUaNUjT7F2F21uOgvXGRjTECQQCG 35 | Uq2qddY1sa5aX7gCm5wOOd1wZpu/pseKMBcONL5Pp+4PlOtL3wPxv6Mt3LO8lfZz 36 | 2KCF1bL1usbn1V7gk6YhAkEAhegwxueVDfayjuOSX0yHKY6VAECELpCqpNy1YEon 37 | 9oKozCDe1Kg/ZLWGMP9cIayb/eyUjJeyUPP8dR2rfhFBkA== 38 | -----END RSA PRIVATE KEY-----` 39 | 40 | ADMIN_MessengerID = "31367" 41 | ) 42 | 43 | type Snapshot struct { 44 | Amount string `json:"amount"` 45 | Asset struct { 46 | AssetId string `json:"asset_id"` 47 | } `json:"asset"` 48 | AssetId string `json:"asset_id"` 49 | CreatedAt time.Time `json:"created_at"` 50 | SnapshotId string `json:"snapshot_id"` 51 | Source string `json:"source"` 52 | Type string `json:"type"` 53 | //only available when http request include correct token 54 | UserId string `json:"user_id"` 55 | TraceId string `json:"trace_id"` 56 | OpponentId string `json:"opponent_id"` 57 | Sender string `json:"sender"` 58 | Data string `json:"data"` 59 | Transactionhash string `json:"transaction_hash"` 60 | } 61 | type Payment_Record struct { 62 | Amount string 63 | AssetId string 64 | CreatedAt time.Time `json:"created_at"` 65 | SnapshotId string `json:"snapshot_id"` 66 | } 67 | 68 | type Profile struct { 69 | CreatedAt time.Time `json:"created_at"` 70 | } 71 | 72 | type DepositAddressResonse struct { 73 | PublicKey string `json:"public_key"` 74 | AccountName string `json:"account_name"` 75 | AccountTag string `json:"account_tag"` 76 | IconURL string `json:"icon_url"` 77 | Confirmblock uint `json:"confirmations"` 78 | Symbol string `json:"symbol"` 79 | Name string `json:"name"` 80 | Chainid string `json:"chain_id"` 81 | Assetkey string `json:"asset_key"` 82 | Assetid string `json:"asset_id"` 83 | Priceusd string `json:"price_usd"` 84 | Pricebtc string `json:"price_btc"` 85 | } 86 | type DepositNetResponse struct { 87 | Error error 88 | Accountid string 89 | Assetid string 90 | MixinResponse MixinDepositResponse 91 | } 92 | type MixinDepositResponse struct { 93 | Data *DepositAddressResonse `json:"data"` 94 | Error string `json:"error"` 95 | } 96 | type Snapshotindb struct { 97 | gorm.Model 98 | SnapshotId string `gorm:"primary_key"` 99 | Amount string 100 | AssetId string `gorm:"index"` 101 | Source string `gorm:"index"` 102 | SnapCreatedAt time.Time 103 | UserId string `gorm:"index"` 104 | TraceId string 105 | OpponentId string 106 | Data string 107 | OPType string 108 | } 109 | 110 | type MixinAccountindb struct { 111 | gorm.Model 112 | Userid string `gorm:"primary_key"` 113 | Sessionid string 114 | Pintoken string 115 | Privatekey string 116 | Pin string 117 | ClientReqid uint 118 | Utccreated_at time.Time 119 | } 120 | 121 | type DepositAddressindb struct { 122 | gorm.Model 123 | Accountrecord_id uint 124 | Assetid string 125 | Publicaddress string 126 | Accountname string 127 | Accounttag string 128 | Iconurl string 129 | Confirmblock uint 130 | } 131 | 132 | type AssetInformationindb struct { 133 | gorm.Model 134 | Assetid string 135 | Chainid string 136 | Symbol string 137 | Name string 138 | Assetkey string 139 | } 140 | 141 | type MessengerUserindb struct { 142 | gorm.Model 143 | Messengerid string `gorm:"primary_key"` 144 | Uuid string 145 | } 146 | 147 | type Assetpriceindb struct { 148 | gorm.Model 149 | Assetid string `gorm:"primary_key"` 150 | Priceinusd string 151 | Priceinbtc string 152 | } 153 | 154 | type CallbackRespone struct { 155 | Callbackurl string 156 | Resp ChargeResponse 157 | } 158 | 159 | type Searchtaskindb struct { 160 | gorm.Model 161 | Starttime time.Time 162 | Endtime time.Time 163 | Yesterday2today bool 164 | Assetid string 165 | Ongoing bool 166 | Userid string 167 | Includesubaccount bool 168 | Taskexpired_at time.Time 169 | } 170 | type Searchtaskinram struct { 171 | Starttime time.Time 172 | Endtime time.Time 173 | Taskexpired_at time.Time 174 | Yesterday2today bool 175 | Assetid string 176 | Ongoing bool 177 | Userid string 178 | Sessionid string 179 | Privatekey string 180 | Includesubaccount bool 181 | } 182 | 183 | type BotConfig struct { 184 | user_id string 185 | session_id string 186 | private_key string 187 | } 188 | 189 | type SnapNetResponse struct { 190 | Error error 191 | MixinRespone MixinResponse 192 | } 193 | 194 | type MixinResponse struct { 195 | Data []*Snapshot `json:"data"` 196 | Error string `json:"error"` 197 | } 198 | 199 | type TransferNetRespone struct { 200 | TransferRes TransferResponse 201 | Error error 202 | } 203 | type TransferResponse struct { 204 | Data Transfer `json:"data"` 205 | Error string `json:"error"` 206 | } 207 | type Transfer struct { 208 | Optype string `json:"type"` 209 | Snapshotid string `json:"snapshot_id"` 210 | OpponentId string `json:"opponent_id"` 211 | Assetid string `json:"asset_id"` 212 | Amount string `json:"amount"` 213 | Memo string `json:"memo"` 214 | Snap_createdat string `json:"created_at"` 215 | } 216 | 217 | type BalanceNetResponse struct { 218 | Balance BalanceResponse 219 | Error error 220 | } 221 | type BalanceResponse struct { 222 | Data []*Asset `json:"data"` 223 | Error string `json:"error"` 224 | } 225 | 226 | type Asset struct { 227 | Optype string `json:"type"` 228 | Assetid string `json:"asset_id"` 229 | Balance string `json:"balance"` 230 | } 231 | type ProfileResponse struct { 232 | Data *Profile `json:"data"` 233 | Error string `json:"error"` 234 | } 235 | type Searchtask struct { 236 | start_t time.Time 237 | end_t time.Time 238 | task_expired_after time.Time 239 | yesterday2today bool 240 | max_len int 241 | asset_id string 242 | ongoing bool 243 | userconfig BotConfig 244 | includesubaccount bool 245 | } 246 | 247 | type Searchprogress struct { 248 | search_task Searchtask 249 | Error error 250 | } 251 | 252 | type PaymentReqhttp struct { 253 | Reqid string `json:"reqid"` 254 | Callback string `json:"callback"` 255 | Expired_after uint32 `json:"expiredafter"` 256 | } 257 | 258 | type PaymentReq struct { 259 | Method string 260 | Reqid string 261 | Callback string 262 | Expired_after uint32 263 | Res_c chan PaymentRes 264 | } 265 | type OPReq struct { 266 | op_code string 267 | Res_c chan []byte 268 | } 269 | type PaymentMethod struct { 270 | Name string 271 | PaymentAddress string 272 | PaymentAccount string 273 | PaymentMemo string 274 | Priceinusd string 275 | Priceinbtc string 276 | } 277 | type PaymentRes struct { 278 | Reqid string 279 | Payment_methods []PaymentMethod 280 | Payment_records []Payment_Record 281 | Balance []Asset 282 | ReceivedinUSD float64 283 | ReceivedinBTC float64 284 | } 285 | type ChargeReqhttp struct { 286 | Currency string `json:"currency"` 287 | Amount float64 `json:"amount"` 288 | Customerid string `json:"customerid"` 289 | Webhookurl string `json:"webhookurl"` 290 | Expired_after uint32 `json:"expiredafter"` 291 | } 292 | type ChargeResponse struct { 293 | Id uint 294 | Currency string 295 | Amount float64 296 | Customerid string 297 | Webhookurl string 298 | Expired_after uint32 299 | Paymentmethod PaymentMethod 300 | Receivedamount float64 301 | Paidstatus uint32 302 | } 303 | 304 | type ChargeReq struct { 305 | Method string 306 | Id string 307 | Currency string 308 | Amount float64 309 | Customerid string 310 | Webhookurl string 311 | Expired_after uint32 312 | Res_c chan ChargeResponse 313 | } 314 | type ChargeRecordindb struct { 315 | gorm.Model 316 | Currency string 317 | Amount float64 318 | Customerid string 319 | Webhookurl string 320 | Expiredafter uint32 321 | MixinAccountid uint 322 | } 323 | type ChargeRelationWithMixinAccountindb struct { 324 | gorm.Model 325 | Chargerecordid uint 326 | Mixinaccountid uint 327 | } 328 | 329 | const ( 330 | BTC_ASSET_ID = "c6d0c728-2624-429b-8e0d-d9d19b6592fa" 331 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d" 332 | USDT_ASSET_ID = "815b0b1a-2764-3736-8faa-42d694fa620a" 333 | ETC_ASSET_ID = "2204c1ee-0ea2-4add-bb9a-b3719cfff93a" 334 | XRP_ASSET_ID = "23dfb5a5-5d7b-48b6-905f-3970e3176e27" 335 | XEM_ASSET_ID = "27921032-f73e-434e-955f-43d55672ee31" 336 | ETH_ASSET_ID = "43d61dcd-e413-450d-80b8-101d5e903357" 337 | DASH_ASSET_ID = "6472e7e3-75fd-48b6-b1dc-28d294ee1476" 338 | DOGE_ASSET_ID = "6770a1e5-6086-44d5-b60f-545f9d9e8ffd" 339 | LTC_ASSET_ID = "76c802a2-7c88-447f-a93e-c29c9e5dd9c8" 340 | SIA_ASSET_ID = "990c4c29-57e9-48f6-9819-7d986ea44985" 341 | ZEN_ASSET_ID = "a2c5d22b-62a2-4c13-b3f0-013290dbac60" 342 | ZEC_ASSET_ID = "c996abc9-d94e-4494-b1cf-2a3fd3ac5714" 343 | BCH_ASSET_ID = "fd11b6e3-0b87-41f1-a41f-f0e9b49e5bf0" 344 | XIN_ASSET_ID = "c94ac88f-4671-3976-b60a-09064f1811e8" 345 | CNB_ASSET_ID = "965e5c6e-434c-3fa9-b780-c50f43cd955c" 346 | XLM_ASSET_ID = "56e63c06-b506-4ec5-885a-4a5ac17b83c1" 347 | TRON_ASSET_ID = "25dabac5-056a-48ff-b9f9-f67395dc407c" 348 | 349 | PREDEFINE_PIN = "198435" 350 | PREDEFINE_NAME = "tom" 351 | scan_interval_in_seconds = 5 352 | op_all_money_go_home = "allmoneygohome" 353 | op_all_snaps = "allsnaps" 354 | assets_price = "assetsprice" 355 | scan_stop_after_n_minutes = 240 356 | local_web_port = ":8080" 357 | ) 358 | 359 | func read_asset_deposit_address(asset_id string, user_id string, session_id string, private_key string, deposit_c chan DepositNetResponse) { 360 | result, err := mixin.Deposit(asset_id, user_id, session_id, private_key) 361 | 362 | if err != nil { 363 | deposit_c <- DepositNetResponse{ 364 | Error: err, 365 | Accountid: user_id, 366 | Assetid: asset_id, 367 | } 368 | return 369 | } 370 | 371 | var resp MixinDepositResponse 372 | err = json.Unmarshal(result, &resp) 373 | 374 | if err != nil { 375 | deposit_c <- DepositNetResponse{ 376 | Error: err, 377 | Accountid: user_id, 378 | Assetid: asset_id, 379 | } 380 | } 381 | if resp.Error != "" { 382 | log.Println("Server return error", resp.Error, " for req:") 383 | return 384 | } 385 | 386 | deposit_c <- DepositNetResponse{ 387 | Accountid: user_id, 388 | Assetid: asset_id, 389 | MixinResponse: resp, 390 | } 391 | } 392 | 393 | //read snapshot related to the account or account created by the account 394 | //given asset id and kick off time: 395 | // the routine will read and filter snapshot endless, 396 | // push snap result into channel 397 | // and progress to another channel 398 | //given asset id and kick off time and end time: 399 | // the routine will read and filter snapshot between the kick off and end time, 400 | // filter snapshot and push data to channel, and progress to another channel 401 | 402 | func read_useruuid_from(user_id string, session_id string, private_key string, messengerid string) string { 403 | botUser := messenger.NewMessenger(user_id, session_id, private_key) 404 | ctx := context.Background() 405 | user, err := botUser.SearchUser(ctx, ADMIN_MessengerID) 406 | 407 | if err != nil { 408 | log.Println(err) 409 | return "" 410 | } 411 | 412 | return user.UserId 413 | } 414 | 415 | func read_bot_created_time(user_id string, session_id string, private_key string) time.Time { 416 | botUser := mixin.NewUser(user_id, session_id, private_key, "") 417 | profile, err := botUser.ReadProfile() 418 | 419 | if err != nil { 420 | return time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC) 421 | } 422 | 423 | var resp ProfileResponse 424 | err = json.Unmarshal(profile, &resp) 425 | 426 | if err != nil { 427 | return time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC) 428 | } 429 | if resp.Error != "" { 430 | return time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC) 431 | } 432 | return resp.Data.CreatedAt 433 | } 434 | 435 | func read_snap_to_future(req_task Searchtask, result_chan chan *Snapshot, in_progress_c chan Searchprogress) { 436 | for { 437 | 438 | if req_task.end_t.IsZero() == false && time.Now().After(req_task.end_t) { 439 | log.Println("Exit task because user set end time and it is passed now ") 440 | p := Searchprogress{ 441 | search_task: req_task, 442 | } 443 | p.search_task.ongoing = false 444 | in_progress_c <- p 445 | return 446 | } 447 | var snaps []byte 448 | var err error 449 | 450 | snaps, err = mixin.NetworkSnapshots(req_task.asset_id, req_task.start_t, "ASC", req_task.max_len, req_task.userconfig.user_id, req_task.userconfig.session_id, req_task.userconfig.private_key) 451 | 452 | if err != nil { 453 | in_progress_c <- Searchprogress{ 454 | Error: err, 455 | } 456 | continue 457 | } 458 | 459 | var resp MixinResponse 460 | err = json.Unmarshal(snaps, &resp) 461 | 462 | if err != nil { 463 | in_progress_c <- Searchprogress{ 464 | Error: err, 465 | } 466 | continue 467 | } 468 | if resp.Error != "" { 469 | log.Fatal("Server return error", resp.Error, " for req:", req_task.asset_id, " start ", req_task.start_t) 470 | return 471 | } 472 | len_of_snap := len(resp.Data) 473 | for _, v := range resp.Data { 474 | 475 | if v.UserId != "" { 476 | result_chan <- v 477 | } 478 | } 479 | if len_of_snap == 0 { 480 | p := Searchprogress{ 481 | search_task: req_task, 482 | } 483 | in_progress_c <- p 484 | //nothing is searched, wait 485 | time.Sleep(scan_interval_in_seconds * time.Second) 486 | continue 487 | } else { 488 | last_element := resp.Data[len(resp.Data)-1] 489 | req_task.start_t = last_element.CreatedAt 490 | p := Searchprogress{ 491 | search_task: req_task, 492 | } 493 | p.search_task.start_t = last_element.CreatedAt 494 | p.search_task.ongoing = true 495 | in_progress_c <- p 496 | if len_of_snap < req_task.max_len { 497 | time.Sleep(scan_interval_in_seconds * time.Second) 498 | } 499 | } 500 | } 501 | } 502 | 503 | func makeChargeHandle(input chan ChargeReq) func(http.ResponseWriter, *http.Request) { 504 | return func(w http.ResponseWriter, r *http.Request) { 505 | switch r.Method { 506 | case "GET": 507 | keys, ok := r.URL.Query()["charge_id"] 508 | if ok != true || len(keys[0]) < 1 { 509 | io.WriteString(w, "Missing parameter customerid!\n") 510 | return 511 | } 512 | charge_res_c := make(chan ChargeResponse, 1) 513 | req := ChargeReq{ 514 | Method: "GET", 515 | Id: keys[0], 516 | Res_c: charge_res_c, 517 | } 518 | input <- req 519 | v := <-charge_res_c 520 | b, jserr := json.Marshal(v) 521 | if jserr != nil { 522 | log.Println(jserr) 523 | } else { 524 | w.Write(b) 525 | } 526 | case "POST": 527 | d := json.NewDecoder(r.Body) 528 | var p ChargeReqhttp 529 | errjs := d.Decode(&p) 530 | if errjs != nil { 531 | http.Error(w, errjs.Error(), http.StatusInternalServerError) 532 | } 533 | charge_res_c := make(chan ChargeResponse, 1) 534 | req := ChargeReq{ 535 | Currency: p.Currency, 536 | Amount: p.Amount, 537 | Customerid: p.Customerid, 538 | Webhookurl: p.Webhookurl, 539 | Expired_after: p.Expired_after, 540 | Res_c: charge_res_c, 541 | } 542 | input <- req 543 | v := <-charge_res_c 544 | b, jserr := json.Marshal(v) 545 | if jserr != nil { 546 | log.Println(jserr) 547 | } else { 548 | w.Write(b) 549 | } 550 | default: 551 | io.WriteString(w, "Wrong!\n") 552 | } 553 | } 554 | } 555 | func moneyGoHomeHandle(input chan OPReq) func(http.ResponseWriter, *http.Request) { 556 | return func(w http.ResponseWriter, r *http.Request) { 557 | switch r.Method { 558 | case "POST": 559 | response_c := make(chan []byte, 2) 560 | input <- OPReq{ 561 | op_code: op_all_money_go_home, 562 | Res_c: response_c, 563 | } 564 | result := <-response_c 565 | w.Write(result) 566 | default: 567 | io.WriteString(w, "Wrong!\n") 568 | } 569 | } 570 | } 571 | 572 | func allsnapsHandle(input chan OPReq) func(http.ResponseWriter, *http.Request) { 573 | return func(w http.ResponseWriter, r *http.Request) { 574 | switch r.Method { 575 | case "GET": 576 | response_c := make(chan []byte, 2) 577 | input <- OPReq{ 578 | op_code: op_all_snaps, 579 | Res_c: response_c, 580 | } 581 | result := <-response_c 582 | w.Write(result) 583 | default: 584 | io.WriteString(w, "Wrong!\n") 585 | } 586 | } 587 | } 588 | func assetspriceHandle(input chan OPReq) func(http.ResponseWriter, *http.Request) { 589 | return func(w http.ResponseWriter, r *http.Request) { 590 | switch r.Method { 591 | case "GET": 592 | response_c := make(chan []byte, 2) 593 | input <- OPReq{ 594 | op_code: assets_price, 595 | Res_c: response_c, 596 | } 597 | result := <-response_c 598 | w.Write(result) 599 | default: 600 | io.WriteString(w, "Wrong!\n") 601 | } 602 | } 603 | } 604 | func paymentHandle(w http.ResponseWriter, req *http.Request) { 605 | 606 | } 607 | 608 | func user_interact(op_c chan OPReq, charge_c chan ChargeReq) { 609 | http.HandleFunc("/charges", makeChargeHandle(charge_c)) 610 | http.HandleFunc("/moneygohome", moneyGoHomeHandle(op_c)) 611 | http.HandleFunc("/snaps", allsnapsHandle(op_c)) 612 | http.HandleFunc("/assetsprice", assetspriceHandle(op_c)) 613 | log.Fatal(http.ListenAndServe(local_web_port, nil)) 614 | log.Println("after web") 615 | } 616 | 617 | func fire_callback_url(v CallbackRespone) { 618 | jsonValue, jserr := json.Marshal(v.Resp) 619 | if jserr != nil { 620 | return 621 | } 622 | localURL := "http://127.0.0.1" + v.Callbackurl 623 | _, err := http.Post(localURL, "application/json", bytes.NewBuffer(jsonValue)) 624 | if err != nil { 625 | log.Println(err) 626 | } 627 | } 628 | func all_money_gomyhome(userid string, sessionid string, privatekey string, pin string, pintoken string, admin_uuid string) { 629 | this_user := mixin.NewUser(userid, sessionid, privatekey, pin, pintoken) 630 | balance, err := this_user.ReadAssets() 631 | if err != nil { 632 | log.Println(err) 633 | return 634 | } else { 635 | var resp BalanceResponse 636 | err = json.Unmarshal(balance, &resp) 637 | if err != nil { 638 | log.Println(err) 639 | return 640 | } 641 | if resp.Error != "" { 642 | log.Println(resp.Error) 643 | return 644 | } 645 | for _, v := range resp.Data { 646 | if v.Balance == "0" { 647 | continue 648 | } else { 649 | trans_result, trans_err := this_user.Transfer(admin_uuid, v.Balance, v.Assetid, "allmoneygomyhome", uuid.Must(uuid.NewV4()).String()) 650 | if trans_err != nil { 651 | log.Println(trans_err) 652 | } else { 653 | var resp TransferNetRespone 654 | err = json.Unmarshal(trans_result, &resp) 655 | 656 | if err != nil { 657 | log.Println(err) 658 | } else { 659 | if resp.TransferRes.Error != "" { 660 | log.Println(resp.TransferRes.Error) 661 | } else { 662 | log.Println(resp.TransferRes.Data.Snapshotid) 663 | } 664 | } 665 | 666 | } 667 | 668 | } 669 | } 670 | } 671 | 672 | } 673 | func create_mixin_account(account_name string, predefine_pin string, user_id string, session_id string, private_key string, result_chan chan MixinAccountindb) { 674 | user, err := mixin.CreateAppUser(account_name, predefine_pin, user_id, session_id, private_key) 675 | if err != nil { 676 | log.Println(err) 677 | } else { 678 | created_time, err := time.Parse(time.RFC3339Nano, user.CreatedAt) 679 | if err != nil { 680 | log.Println(err) 681 | } else { 682 | new_user := MixinAccountindb{ 683 | Userid: user.UserId, 684 | Sessionid: user.SessionId, 685 | Pintoken: user.PinToken, 686 | Privatekey: user.PrivateKey, 687 | Pin: predefine_pin, 688 | ClientReqid: 0, 689 | Utccreated_at: created_time, 690 | } 691 | result_chan <- new_user 692 | } 693 | 694 | } 695 | } 696 | 697 | func search_userincome(asset_id string, userid string, sessionid string, privatekey string, in_result_chan chan *Snapshot, in_progress_c chan Searchprogress, use_created_at time.Time, end_at time.Time, search_expired_after time.Time) { 698 | req_task := Searchtask{ 699 | start_t: end_at, 700 | end_t: use_created_at, 701 | max_len: 500, 702 | yesterday2today: false, 703 | asset_id: asset_id, 704 | userconfig: BotConfig{ 705 | user_id: userid, 706 | session_id: sessionid, 707 | private_key: privatekey, 708 | }, 709 | ongoing: true, 710 | includesubaccount: false, 711 | task_expired_after: search_expired_after, 712 | } 713 | for { 714 | if req_task.task_expired_after.IsZero() == false && time.Now().After(req_task.task_expired_after) { 715 | p := Searchprogress{ 716 | search_task: req_task, 717 | } 718 | p.search_task.ongoing = false 719 | in_progress_c <- p 720 | log.Println("task is expired") 721 | return 722 | } 723 | var snaps []byte 724 | var err error 725 | snaps, err = mixin.MyNetworkSnapshots(req_task.asset_id, req_task.start_t, req_task.max_len, req_task.userconfig.user_id, req_task.userconfig.session_id, req_task.userconfig.private_key) 726 | 727 | if err != nil { 728 | in_progress_c <- Searchprogress{ 729 | Error: err, 730 | } 731 | continue 732 | } 733 | var resp MixinResponse 734 | err = json.Unmarshal(snaps, &resp) 735 | 736 | if err != nil { 737 | in_progress_c <- Searchprogress{ 738 | Error: err, 739 | } 740 | continue 741 | } 742 | if resp.Error != "" { 743 | log.Fatal("Server return error", resp.Error, " for req:", req_task.asset_id, " start ", req_task.start_t) 744 | return 745 | } 746 | len_of_snap := len(resp.Data) 747 | for _, v := range resp.Data { 748 | v.UserId = req_task.userconfig.user_id 749 | in_result_chan <- v 750 | } 751 | if len_of_snap == 0 { 752 | req_task.start_t = time.Now() 753 | p := Searchprogress{ 754 | search_task: req_task, 755 | } 756 | in_progress_c <- p 757 | time.Sleep(30 * time.Second) 758 | } else { 759 | last_element := resp.Data[len(resp.Data)-1] 760 | req_task.start_t = last_element.CreatedAt 761 | p := Searchprogress{ 762 | search_task: req_task, 763 | } 764 | p.search_task.start_t = last_element.CreatedAt 765 | 766 | if req_task.end_t.IsZero() == false && last_element.CreatedAt.Before(req_task.end_t) { 767 | p.search_task.ongoing = false 768 | in_progress_c <- p 769 | return 770 | } 771 | in_progress_c <- p 772 | } 773 | } 774 | } 775 | func restore_searchsnap(bot_config BotConfig, in_result_chan chan *Snapshot, in_progress_c chan Searchprogress, default_asset_id_group []string, searchtasks_array_inram []Searchtaskinram) { 776 | if len(searchtasks_array_inram) > 0 { 777 | for _, v := range searchtasks_array_inram { 778 | if v.Ongoing == true { 779 | log.Println(v.Ongoing, v.Starttime, v.Endtime, v.Userid, v.Assetid) 780 | unfinished_req_task := Searchtask{ 781 | start_t: v.Starttime, 782 | end_t: v.Endtime, 783 | max_len: 500, 784 | yesterday2today: v.Yesterday2today, 785 | asset_id: v.Assetid, 786 | ongoing: v.Ongoing, 787 | userconfig: BotConfig{ 788 | user_id: v.Userid, 789 | session_id: v.Sessionid, 790 | private_key: v.Privatekey, 791 | }, 792 | includesubaccount: v.Includesubaccount, 793 | task_expired_after: v.Taskexpired_at, 794 | } 795 | if v.Includesubaccount == false { 796 | go search_userincome(v.Assetid, v.Userid, v.Sessionid, v.Privatekey, in_result_chan, in_progress_c, v.Endtime, time.Now(), v.Taskexpired_at) 797 | } else { 798 | if v.Yesterday2today { 799 | go read_snap_to_future(unfinished_req_task, in_result_chan, in_progress_c) 800 | } 801 | } 802 | 803 | } 804 | } 805 | } else { 806 | botCreateAt := read_bot_created_time(bot_config.user_id, bot_config.session_id, bot_config.private_key) 807 | if botCreateAt.IsZero() { 808 | panic("Read bot profile failed") 809 | } else { 810 | log.Println("I am created at ", botCreateAt) 811 | for _, v := range default_asset_id_group { 812 | search_asset_task := Searchtask{ 813 | start_t: botCreateAt, 814 | max_len: 500, 815 | yesterday2today: true, 816 | asset_id: v, 817 | userconfig: bot_config, 818 | includesubaccount: true, 819 | } 820 | go read_snap_to_future(search_asset_task, in_result_chan, in_progress_c) 821 | } 822 | } 823 | } 824 | } 825 | 826 | func main() { 827 | var my_snapshot_chan = make(chan *Snapshot, 1000) 828 | var asset_received_snap_chan = make(chan *Snapshot, 1000) 829 | var global_progress_c = make(chan Searchprogress, 1000) 830 | var quit_chan = make(chan int, 2) 831 | var charge_cmd_chan = make(chan ChargeReq, 2) 832 | var single_direction_op_cmd_chan = make(chan OPReq, 2) 833 | var new_account_received_chan = make(chan MixinAccountindb, 100) 834 | var payment_received_asset_chan = make(chan CallbackRespone, 100) 835 | var account_deposit_address_receive_chan = make(chan DepositNetResponse, 100) 836 | var should_create_more_account_c = make(chan uint, 10) 837 | var update_asset_price_c = make(chan uint, 10) 838 | // to support more asset, just add them in the following array 839 | default_asset_id_group := []string{XLM_ASSET_ID, EOS_ASSET_ID, ETH_ASSET_ID} 840 | timer1 := time.NewTimer(1 * time.Minute) 841 | 842 | db, err := gorm.Open("sqlite3", "test.db") 843 | if err != nil { 844 | panic("failed to connect database") 845 | } 846 | defer db.Close() 847 | 848 | db.AutoMigrate(&Snapshotindb{}) 849 | db.AutoMigrate(&Searchtaskindb{}) 850 | db.AutoMigrate(&MixinAccountindb{}) 851 | db.AutoMigrate(&DepositAddressindb{}) 852 | db.AutoMigrate(&AssetInformationindb{}) 853 | db.AutoMigrate(&MessengerUserindb{}) 854 | db.AutoMigrate(&Assetpriceindb{}) 855 | db.AutoMigrate(&ChargeRecordindb{}) 856 | db.AutoMigrate(&ChargeRelationWithMixinAccountindb{}) 857 | var bot_config_instance = BotConfig{ 858 | user_id: userid, 859 | session_id: sessionid, 860 | private_key: private_key, 861 | } 862 | 863 | //startup 864 | var admin_uuid_record MessengerUserindb 865 | db.Find(&MessengerUserindb{Messengerid: ADMIN_MessengerID}).First(&admin_uuid_record) 866 | if admin_uuid_record.ID == 0 { 867 | result := read_useruuid_from(bot_config_instance.user_id, bot_config_instance.session_id, bot_config_instance.private_key, ADMIN_MessengerID) 868 | if result != "" { 869 | log.Println(result) 870 | db.Create(&MessengerUserindb{Messengerid: ADMIN_MessengerID, Uuid: result}) 871 | } else { 872 | log.Fatal("Failed to read admin uuid by it's messenger id") 873 | } 874 | } 875 | db.Find(&MessengerUserindb{Messengerid: ADMIN_MessengerID}).First(&admin_uuid_record) 876 | var ongoing_searchtasks_indb []Searchtaskindb 877 | var ongoing_searchtasks_inram []Searchtaskinram 878 | db.Find(&ongoing_searchtasks_indb) 879 | for _, v := range ongoing_searchtasks_indb { 880 | var this_user_record MixinAccountindb 881 | if db.Where(&MixinAccountindb{Userid: v.Userid}).First(&this_user_record).RecordNotFound() == false { 882 | var this_search_task_ram Searchtaskinram 883 | this_search_task_ram.Starttime = v.Starttime 884 | this_search_task_ram.Endtime = v.Endtime 885 | this_search_task_ram.Taskexpired_at = v.Taskexpired_at 886 | this_search_task_ram.Yesterday2today = v.Yesterday2today 887 | this_search_task_ram.Assetid = v.Assetid 888 | this_search_task_ram.Ongoing = v.Ongoing 889 | 890 | this_search_task_ram.Userid = v.Userid 891 | this_search_task_ram.Sessionid = this_user_record.Sessionid 892 | this_search_task_ram.Privatekey = this_user_record.Privatekey 893 | 894 | this_search_task_ram.Includesubaccount = v.Includesubaccount 895 | 896 | ongoing_searchtasks_inram = append(ongoing_searchtasks_inram, this_search_task_ram) 897 | } else { 898 | if v.Userid == bot_config_instance.user_id { 899 | var this_search_task_ram Searchtaskinram 900 | this_search_task_ram.Starttime = v.Starttime 901 | this_search_task_ram.Endtime = v.Endtime 902 | this_search_task_ram.Taskexpired_at = v.Taskexpired_at 903 | this_search_task_ram.Yesterday2today = v.Yesterday2today 904 | this_search_task_ram.Assetid = v.Assetid 905 | this_search_task_ram.Ongoing = v.Ongoing 906 | this_search_task_ram.Userid = v.Userid 907 | this_search_task_ram.Sessionid = bot_config_instance.session_id 908 | this_search_task_ram.Privatekey = bot_config_instance.private_key 909 | this_search_task_ram.Includesubaccount = v.Includesubaccount 910 | ongoing_searchtasks_inram = append(ongoing_searchtasks_inram, this_search_task_ram) 911 | } 912 | } 913 | } 914 | 915 | restore_searchsnap(bot_config_instance, my_snapshot_chan, global_progress_c, default_asset_id_group, ongoing_searchtasks_inram) 916 | go user_interact(single_direction_op_cmd_chan, charge_cmd_chan) 917 | 918 | should_create_more_account_c <- 1 919 | update_asset_price_c <- 1 920 | for { 921 | select { 922 | case pv := <-global_progress_c: 923 | if pv.Error != nil { 924 | log.Println(pv.Error) 925 | continue 926 | } 927 | searchtaskindb := Searchtaskindb{} 928 | query_task := Searchtaskindb{ 929 | Endtime: pv.search_task.end_t, 930 | Assetid: pv.search_task.asset_id, 931 | Userid: pv.search_task.userconfig.user_id, 932 | Includesubaccount: pv.search_task.includesubaccount, 933 | } 934 | if db.Where(&query_task).First(&searchtaskindb).RecordNotFound() { 935 | var this_record = Searchtaskindb{ 936 | Starttime: pv.search_task.start_t, 937 | Endtime: pv.search_task.end_t, 938 | Yesterday2today: pv.search_task.yesterday2today, 939 | Assetid: pv.search_task.asset_id, 940 | Ongoing: pv.search_task.ongoing, 941 | Userid: pv.search_task.userconfig.user_id, 942 | Includesubaccount: pv.search_task.includesubaccount, 943 | Taskexpired_at: pv.search_task.task_expired_after, 944 | } 945 | db.Create(&this_record) 946 | } else { 947 | searchtaskindb.Starttime = pv.search_task.start_t 948 | searchtaskindb.Ongoing = pv.search_task.ongoing 949 | db.Save(&searchtaskindb) 950 | } 951 | case v := <-my_snapshot_chan: 952 | var snapInDb Snapshotindb 953 | if db.Where(&Snapshotindb{SnapshotId: v.SnapshotId}).First(&snapInDb).RecordNotFound() == true { 954 | var thisrecord = Snapshotindb{ 955 | SnapshotId: v.SnapshotId, 956 | Amount: v.Amount, 957 | AssetId: v.Asset.AssetId, 958 | Source: v.Source, 959 | SnapCreatedAt: v.CreatedAt, 960 | UserId: v.UserId, 961 | TraceId: v.TraceId, 962 | OpponentId: v.OpponentId, 963 | Data: v.Data, 964 | OPType: v.Type, 965 | } 966 | if v.AssetId != "" { 967 | thisrecord.AssetId = v.AssetId 968 | } 969 | db.Create(&thisrecord) 970 | f, err := strconv.ParseFloat(v.Amount, 64) 971 | if err != nil { 972 | log.Println(err) 973 | } else { 974 | if f > 0 { 975 | asset_received_snap_chan <- v 976 | } 977 | } 978 | } 979 | case v := <-asset_received_snap_chan: 980 | var matched_account MixinAccountindb 981 | if db.Where(&MixinAccountindb{Userid: v.UserId}).First(&matched_account).RecordNotFound() == false { 982 | go all_money_gomyhome(matched_account.Userid, matched_account.Sessionid, matched_account.Privatekey, matched_account.Pin, matched_account.Pintoken, admin_uuid_record.Uuid) 983 | if matched_account.ClientReqid != 0 { 984 | var charge_record ChargeRecordindb 985 | db.Find(&charge_record, matched_account.ClientReqid) 986 | if charge_record.ID != 0 { 987 | var callback_response CallbackRespone 988 | callback_response.Callbackurl = charge_record.Webhookurl 989 | 990 | var resp ChargeResponse 991 | resp.Id = charge_record.ID 992 | resp.Currency = charge_record.Currency 993 | resp.Amount = charge_record.Amount 994 | resp.Customerid = charge_record.Customerid 995 | resp.Expired_after = charge_record.Expiredafter 996 | resp.Webhookurl = charge_record.Webhookurl 997 | 998 | //looking for currency asset id from SYMBOL 999 | var currentcy_asset_info AssetInformationindb 1000 | if db.Where(&AssetInformationindb{Symbol: charge_record.Currency}).First(¤tcy_asset_info).RecordNotFound() == false { 1001 | var payment_address DepositAddressindb 1002 | //find the currency payment address, this is a deposit address for crypto asset, different with lightning charge invoice 1003 | if db.Where(&DepositAddressindb{Accountrecord_id: matched_account.ID, Assetid: currentcy_asset_info.Assetid}).First(&payment_address).RecordNotFound() == false { 1004 | resp.Paymentmethod = PaymentMethod{ 1005 | Name: currentcy_asset_info.Symbol, 1006 | PaymentAddress: payment_address.Publicaddress, 1007 | PaymentAccount: payment_address.Accountname, 1008 | PaymentMemo: payment_address.Accounttag, 1009 | } 1010 | } 1011 | 1012 | //fill market price 1013 | var asset_price_record Assetpriceindb 1014 | if db.Where(&Assetpriceindb{Assetid: currentcy_asset_info.Assetid}).First(&asset_price_record).RecordNotFound() == false { 1015 | resp.Paymentmethod.Priceinusd = asset_price_record.Priceinusd 1016 | resp.Paymentmethod.Priceinbtc = asset_price_record.Priceinbtc 1017 | } 1018 | 1019 | resp.Paidstatus = 0 1020 | resp.Receivedamount = 0 1021 | var all_assets_payment_snapshots_indb []Snapshotindb 1022 | db.Where(&Snapshotindb{UserId: matched_account.Userid, AssetId: currentcy_asset_info.Assetid}).Find(&all_assets_payment_snapshots_indb) 1023 | for _, v := range all_assets_payment_snapshots_indb { 1024 | f, err := strconv.ParseFloat(v.Amount, 64) 1025 | if err != nil { 1026 | log.Fatal(err) 1027 | continue 1028 | } else { 1029 | if f > 0 { 1030 | resp.Receivedamount += f 1031 | } 1032 | } 1033 | } 1034 | 1035 | if resp.Receivedamount > resp.Amount { 1036 | resp.Paidstatus = 3 1037 | } else if resp.Receivedamount == resp.Amount { 1038 | resp.Paidstatus = 2 1039 | } else if resp.Receivedamount > 0 { 1040 | resp.Paidstatus = 1 1041 | } 1042 | 1043 | callback_response.Resp = resp 1044 | payment_received_asset_chan <- callback_response 1045 | } 1046 | } 1047 | } 1048 | } 1049 | 1050 | case v := <-payment_received_asset_chan: 1051 | go fire_callback_url(v) 1052 | case <-quit_chan: 1053 | log.Println("finished") 1054 | return 1055 | 1056 | case new_user := <-new_account_received_chan: 1057 | db.Create(&new_user) 1058 | for _, v := range default_asset_id_group { 1059 | depositRecord := DepositAddressindb{ 1060 | Accountrecord_id: new_user.ID, 1061 | Assetid: v, 1062 | } 1063 | db.Create(&depositRecord) 1064 | go read_asset_deposit_address(v, new_user.Userid, new_user.Sessionid, new_user.Privatekey, account_deposit_address_receive_chan) 1065 | } 1066 | case asset_deposit_address_result := <-account_deposit_address_receive_chan: 1067 | if asset_deposit_address_result.Error == nil { 1068 | var matched_user MixinAccountindb 1069 | db.Where(&MixinAccountindb{Userid: asset_deposit_address_result.Accountid}).First(&matched_user) 1070 | var depositRecord DepositAddressindb 1071 | db.Where("accountrecord_id = ?", matched_user.ID).Where("assetid = ?", asset_deposit_address_result.Assetid).First(&depositRecord) 1072 | if depositRecord.CreatedAt.IsZero() { 1073 | panic("The record should has been created when the user is created") 1074 | } else { 1075 | depositRecord.Publicaddress = asset_deposit_address_result.MixinResponse.Data.PublicKey 1076 | depositRecord.Accountname = asset_deposit_address_result.MixinResponse.Data.AccountName 1077 | depositRecord.Accounttag = asset_deposit_address_result.MixinResponse.Data.AccountTag 1078 | depositRecord.Confirmblock = asset_deposit_address_result.MixinResponse.Data.Confirmblock 1079 | depositRecord.Iconurl = asset_deposit_address_result.MixinResponse.Data.IconURL 1080 | db.Save(&depositRecord) 1081 | 1082 | var asset_record AssetInformationindb 1083 | if db.Where(&AssetInformationindb{Assetid: depositRecord.Assetid}).First(&asset_record).RecordNotFound() { 1084 | //first found asset 1085 | asset_record.Symbol = asset_deposit_address_result.MixinResponse.Data.Symbol 1086 | asset_record.Name = asset_deposit_address_result.MixinResponse.Data.Name 1087 | asset_record.Assetkey = asset_deposit_address_result.MixinResponse.Data.Assetkey 1088 | asset_record.Chainid = asset_deposit_address_result.MixinResponse.Data.Chainid 1089 | asset_record.Assetid = asset_deposit_address_result.Assetid 1090 | db.Create(&asset_record) 1091 | } 1092 | } 1093 | } 1094 | 1095 | case <-timer1.C: 1096 | should_create_more_account_c <- 1 1097 | update_asset_price_c <- 1 1098 | 1099 | case <-update_asset_price_c: 1100 | for _, asset_id := range default_asset_id_group { 1101 | result, err := mixin.Deposit(asset_id, bot_config_instance.user_id, bot_config_instance.session_id, bot_config_instance.private_key) 1102 | 1103 | if err != nil { 1104 | log.Fatal(err) 1105 | continue 1106 | } 1107 | 1108 | var resp MixinDepositResponse 1109 | err = json.Unmarshal(result, &resp) 1110 | 1111 | if err != nil { 1112 | log.Fatal(err) 1113 | continue 1114 | } 1115 | if resp.Error != "" { 1116 | log.Fatal(resp.Error) 1117 | continue 1118 | } 1119 | var this_asset_price Assetpriceindb 1120 | this_asset_price.Assetid = asset_id 1121 | 1122 | if db.Where(&Assetpriceindb{Assetid: asset_id}).First(&this_asset_price).RecordNotFound() == true { 1123 | var new_asset_price Assetpriceindb 1124 | new_asset_price.Assetid = asset_id 1125 | new_asset_price.Priceinusd = resp.Data.Priceusd 1126 | new_asset_price.Priceinbtc = resp.Data.Pricebtc 1127 | db.Create(&new_asset_price) 1128 | } else { 1129 | this_asset_price.Priceinusd = resp.Data.Priceusd 1130 | this_asset_price.Priceinbtc = resp.Data.Pricebtc 1131 | db.Save(&this_asset_price) 1132 | } 1133 | } 1134 | case <-should_create_more_account_c: 1135 | var free_mixinaccounts []MixinAccountindb 1136 | db.Model(&MixinAccountindb{}).Where("client_reqid = ?", "0").Find(&free_mixinaccounts) 1137 | available_mixin_account := len(free_mixinaccounts) 1138 | if available_mixin_account < 10 { 1139 | for i := 20; i > available_mixin_account; i-- { 1140 | go create_mixin_account(PREDEFINE_NAME, PREDEFINE_PIN, bot_config_instance.user_id, bot_config_instance.session_id, bot_config_instance.private_key, new_account_received_chan) 1141 | } 1142 | } 1143 | 1144 | //read all free account, and check all deposit address is ready 1145 | for _, account := range free_mixinaccounts { 1146 | var payment_addresses []DepositAddressindb 1147 | db.Where(&DepositAddressindb{Accountrecord_id: account.ID}).Find(&payment_addresses) 1148 | for _, payment_address := range payment_addresses { 1149 | if payment_address.Publicaddress == "" && payment_address.Accountname == "" && payment_address.Accounttag == "" { 1150 | log.Println("some account deposit address is still missing") 1151 | go read_asset_deposit_address(payment_address.Assetid, account.Userid, account.Sessionid, account.Privatekey, account_deposit_address_receive_chan) 1152 | } 1153 | } 1154 | } 1155 | case v := <-single_direction_op_cmd_chan: 1156 | switch v.op_code { 1157 | case op_all_money_go_home: 1158 | var allaccount []MixinAccountindb 1159 | db.Find(&allaccount) 1160 | for _, v := range allaccount { 1161 | go all_money_gomyhome(v.Userid, v.Sessionid, v.Privatekey, v.Pin, v.Pintoken, admin_uuid_record.Uuid) 1162 | } 1163 | type Message struct { 1164 | Action string 1165 | Result string 1166 | } 1167 | m := Message{"Allmoneygohome", fmt.Sprintf("total %d account will send all balance to admin", len(allaccount))} 1168 | b, _ := json.Marshal(m) 1169 | v.Res_c <- b 1170 | case op_all_snaps: 1171 | var allbot_snaps []Snapshotindb 1172 | db.Find(&allbot_snaps) 1173 | type Snap struct { 1174 | Snapshotid string 1175 | Createdat time.Time 1176 | Amount string 1177 | Assetid string 1178 | } 1179 | type Message struct { 1180 | Action string 1181 | Result []Snap 1182 | } 1183 | var result []Snap 1184 | for _, v := range allbot_snaps { 1185 | this := Snap{v.SnapshotId, v.SnapCreatedAt, v.Amount, v.AssetId} 1186 | result = append(result, this) 1187 | } 1188 | 1189 | m := Message{"Allsnap", result} 1190 | b, _ := json.Marshal(m) 1191 | v.Res_c <- b 1192 | case assets_price: 1193 | type Assetprice2client struct { 1194 | Fullname string 1195 | Symbol string 1196 | USDPrice float64 1197 | BTCPrice float64 1198 | Assetid string 1199 | } 1200 | 1201 | var asset_prices []Assetprice2client 1202 | for _, v := range default_asset_id_group { 1203 | var asset_info AssetInformationindb 1204 | if db.Where(&AssetInformationindb{Assetid: v}).First(&asset_info).RecordNotFound() == false { 1205 | var asset_price Assetpriceindb 1206 | if db.Where(&Assetpriceindb{Assetid: v}).First(&asset_price).RecordNotFound() == false { 1207 | usdprice, _ := strconv.ParseFloat(asset_price.Priceinusd, 64) 1208 | btcprice, _ := strconv.ParseFloat(asset_price.Priceinbtc, 64) 1209 | this := Assetprice2client{ 1210 | Fullname: asset_info.Name, 1211 | Symbol: asset_info.Symbol, 1212 | Assetid: asset_info.Assetid, 1213 | USDPrice: usdprice, 1214 | BTCPrice: btcprice, 1215 | } 1216 | asset_prices = append(asset_prices, this) 1217 | } 1218 | } 1219 | } 1220 | b, _ := json.Marshal(asset_prices) 1221 | v.Res_c <- b 1222 | } 1223 | 1224 | case v := <-charge_cmd_chan: 1225 | if v.Method == "GET" { 1226 | var charge_record ChargeRecordindb 1227 | var resp ChargeResponse 1228 | charge_record_id, _ := strconv.ParseUint(v.Id, 10, 32) 1229 | db.Find(&charge_record, charge_record_id) 1230 | 1231 | if charge_record.ID != 0 { 1232 | resp.Id = charge_record.ID 1233 | resp.Currency = charge_record.Currency 1234 | resp.Amount = charge_record.Amount 1235 | resp.Customerid = charge_record.Customerid 1236 | resp.Expired_after = charge_record.Expiredafter 1237 | resp.Webhookurl = charge_record.Webhookurl 1238 | 1239 | //looking for currency asset id from SYMBOL 1240 | var currentcy_asset_info AssetInformationindb 1241 | if db.Where(&AssetInformationindb{Symbol: charge_record.Currency}).First(¤tcy_asset_info).RecordNotFound() == false { 1242 | var mixin_account MixinAccountindb 1243 | 1244 | if db.Where(&MixinAccountindb{ClientReqid: charge_record.ID}).First(&mixin_account).RecordNotFound() == false { 1245 | var payment_address DepositAddressindb 1246 | //find the currency payment address, this is a deposit address for crypto asset, different with lightning charge invoice 1247 | if db.Where(&DepositAddressindb{Accountrecord_id: mixin_account.ID, Assetid: currentcy_asset_info.Assetid}).First(&payment_address).RecordNotFound() == false { 1248 | resp.Paymentmethod = PaymentMethod{ 1249 | Name: currentcy_asset_info.Symbol, 1250 | PaymentAddress: payment_address.Publicaddress, 1251 | PaymentAccount: payment_address.Accountname, 1252 | PaymentMemo: payment_address.Accounttag, 1253 | } 1254 | } 1255 | 1256 | //fill market price 1257 | var asset_price_record Assetpriceindb 1258 | if db.Where(&Assetpriceindb{Assetid: currentcy_asset_info.Assetid}).First(&asset_price_record).RecordNotFound() == false { 1259 | resp.Paymentmethod.Priceinusd = asset_price_record.Priceinusd 1260 | resp.Paymentmethod.Priceinbtc = asset_price_record.Priceinbtc 1261 | } 1262 | 1263 | resp.Paidstatus = 0 1264 | resp.Receivedamount = 0 1265 | var all_assets_payment_snapshots_indb []Snapshotindb 1266 | db.Where(&Snapshotindb{UserId: mixin_account.Userid, AssetId: currentcy_asset_info.Assetid}).Find(&all_assets_payment_snapshots_indb) 1267 | for _, v := range all_assets_payment_snapshots_indb { 1268 | f, err := strconv.ParseFloat(v.Amount, 64) 1269 | if err != nil { 1270 | log.Fatal(err) 1271 | continue 1272 | } else { 1273 | if f > 0 { 1274 | resp.Receivedamount += f 1275 | } 1276 | } 1277 | } 1278 | if resp.Receivedamount > resp.Amount { 1279 | resp.Paidstatus = 3 1280 | } else if resp.Receivedamount == resp.Amount { 1281 | resp.Paidstatus = 2 1282 | } else if resp.Receivedamount > 0 { 1283 | resp.Paidstatus = 1 1284 | } 1285 | } 1286 | } 1287 | 1288 | } 1289 | v.Res_c <- resp 1290 | } else { 1291 | var resp ChargeResponse 1292 | var currentcy_asset_info AssetInformationindb 1293 | //understand currency symbol 1294 | if db.Where(&AssetInformationindb{Symbol: v.Currency}).First(¤tcy_asset_info).RecordNotFound() == false { 1295 | var new_charge ChargeRecordindb 1296 | new_charge.Currency = v.Currency 1297 | new_charge.Amount = v.Amount 1298 | new_charge.Customerid = v.Customerid 1299 | new_charge.Expiredafter = v.Expired_after 1300 | new_charge.Webhookurl = v.Webhookurl 1301 | db.Create(&new_charge) 1302 | 1303 | resp.Id = new_charge.ID 1304 | resp.Currency = new_charge.Currency 1305 | resp.Amount = new_charge.Amount 1306 | resp.Customerid = new_charge.Customerid 1307 | resp.Expired_after = new_charge.Expiredafter 1308 | resp.Webhookurl = new_charge.Webhookurl 1309 | 1310 | var free_mixinaccount MixinAccountindb 1311 | if db.Where("client_reqid = ?", "0").First(&free_mixinaccount).RecordNotFound() == false { 1312 | free_mixinaccount.ClientReqid = new_charge.ID 1313 | db.Save(&free_mixinaccount) 1314 | go search_userincome("", free_mixinaccount.Userid, free_mixinaccount.Sessionid, free_mixinaccount.Privatekey, my_snapshot_chan, global_progress_c, free_mixinaccount.Utccreated_at, time.Now(), time.Now().Add(time.Duration(v.Expired_after)*time.Minute)) 1315 | var payment_address DepositAddressindb 1316 | //find the currency payment address, this is a deposit address for crypto asset, different with lightning charge invoice 1317 | if db.Where(&DepositAddressindb{Accountrecord_id: free_mixinaccount.ID, Assetid: currentcy_asset_info.Assetid}).First(&payment_address).RecordNotFound() == false { 1318 | resp.Paymentmethod = PaymentMethod{ 1319 | Name: currentcy_asset_info.Symbol, 1320 | PaymentAddress: payment_address.Publicaddress, 1321 | PaymentAccount: payment_address.Accountname, 1322 | PaymentMemo: payment_address.Accounttag, 1323 | } 1324 | } 1325 | //fill market price 1326 | var asset_price_record Assetpriceindb 1327 | if db.Where(&Assetpriceindb{Assetid: currentcy_asset_info.Assetid}).First(&asset_price_record).RecordNotFound() == false { 1328 | resp.Paymentmethod.Priceinusd = asset_price_record.Priceinusd 1329 | resp.Paymentmethod.Priceinbtc = asset_price_record.Priceinbtc 1330 | } 1331 | } 1332 | } 1333 | v.Res_c <- resp 1334 | } 1335 | } 1336 | } 1337 | } 1338 | --------------------------------------------------------------------------------