Payments.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. <?php
  2. /**
  3. * Payments.php UTF-8
  4. *
  5. *
  6. * @date : 2017/5/2 14:50
  7. *
  8. * @license 这不是一个自由软件,未经授权不许任何使用和传播。
  9. * @author : wuyonghong <wyh@huosdk.com>
  10. * @version : HUOSDK 7.0
  11. */
  12. namespace huoMpay;
  13. use huo\controller\common\Base;
  14. use huo\controller\common\HuoSession;
  15. use huo\controller\pay\Notify;
  16. use huo\controller\pay\Sdk;
  17. use huo\controller\pay\SdkOrderCache;
  18. use huolib\constant\OrderConst;
  19. use huolib\constant\PaywayConst;
  20. use huolib\status\OrderStatus;
  21. use huolib\tool\StrUtils;
  22. use huomp\controller\game\GameMini;
  23. use huomp\model\wallet\MgmLogModel;
  24. use huomp\model\wallet\MidasGmMemModel;
  25. use think\Log;
  26. class Payments extends Base {
  27. const MGM_QUERY = 1; /* 1 查询 */
  28. const MGM_PAY = 2; /* 2 扣费 */
  29. const MGM_PRESENT = 3; /* 3 赠送 */
  30. const MGM_CANCEL = 4; /* 4 取消支付 */
  31. /**
  32. * 获取余额并且支付
  33. *
  34. * @param int $mem_id 玩家ID
  35. * @param string $order_id 订单ID
  36. *
  37. * @return array
  38. */
  39. public function getBalanceAndPay($mem_id, $order_id) {
  40. if (empty($order_id)) {
  41. $_code = OrderStatus::ORDER_ID_EMPTY;
  42. return $this->huoError($_code, OrderStatus::getMsg($_code));
  43. }
  44. $_soc_class = SdkOrderCache::ins();
  45. $_order_data = $_soc_class->getInfoByOrderId($order_id);
  46. if (empty($_order_data)) {
  47. $_code = OrderStatus::ORDER_ID_ERROR;
  48. return $this->huoError($_code, OrderStatus::getMsg($_code));
  49. }
  50. if ($mem_id != $_order_data['mem_id']) {
  51. $_code = OrderStatus::ORDER_NOT_EXISTS;
  52. return $this->huoError($_code, OrderStatus::getMsg($_code));
  53. }
  54. $mem_id = $_order_data['mem_id'];
  55. $_rdata['status'] = $_order_data['status'];
  56. $_rdata['cp_status'] = $_order_data['cp_status'];
  57. if (OrderConst::CP_STATUS_SUC != $_order_data['cp_status']
  58. && OrderConst::PAY_STATUS_SUC == $_order_data['status']) {
  59. $_rs = (new Notify())->notify($order_id);
  60. if (OrderStatus::NO_ERROR == $_rs['code']) {
  61. $_rdata['status'] = OrderConst::PAY_STATUS_SUC;
  62. $_rdata['cp_status'] = OrderConst::CP_STATUS_SUC;
  63. }
  64. $_code = OrderStatus::NO_ERROR;
  65. return $this->huoSuccess($_code, OrderStatus::getMsg($_code), $_rdata);
  66. }
  67. /* 获取余额 */
  68. $_rs = $this->midasGetBalance($mem_id, $order_id);
  69. if (OrderStatus::NO_ERROR != $_rs['code']) {
  70. return $this->huoError($_rs['code'], $_rs['msg']);
  71. }
  72. $_data = $_rs['data'];
  73. $_mgm_model = (new MidasGmMemModel());
  74. $_mgm_data = $_mgm_model->getDataOrInsert($mem_id, $_order_data['app_id']);
  75. $_diff_gm_cnt = StrUtils::formatNumber($_data['save_amt'] - $_mgm_data['m_save_amt']);
  76. /* 数量大于等于充值金额 */
  77. $_product_cnt = StrUtils::formatNumber($_order_data['product_cnt']);
  78. /* 变化的游戏币必须币充值的游戏币多 */
  79. if ($_diff_gm_cnt < $_product_cnt) {
  80. $_code = OrderStatus::NOTIFY_AMOUNT_ERROR;
  81. Log::write(
  82. "func=".__FUNCTION__."&class=".__CLASS__."&step1&code=".$_code.'&data.'
  83. .http_build_query($_data).'&order_data='.http_build_query($_order_data).'&mgm_data='.http_build_query(
  84. $_mgm_data
  85. ).'&diff_gm_cnt='.$_diff_gm_cnt.'&product_cnt='.$_product_cnt.'&rs='.json_encode($_rs),
  86. LOG::ERROR
  87. );
  88. return $this->huoError($_code, OrderStatus::getMsg($_code));
  89. }
  90. /* 扣除所有游戏币 */
  91. /* 获取余额 */
  92. $_rs = $this->midasPay($mem_id, $order_id);
  93. if (OrderStatus::NO_ERROR != $_rs['code'] && 90012 != $_rs['code']) {
  94. return $this->huoError($_rs['code'], $_rs['msg']);
  95. }
  96. $_rs = (new Notify())->payNotify(
  97. $_order_data['product_id'], $order_id, '', $_order_data['amount'], $_order_data['payway']
  98. );
  99. if (false == $_rs) {
  100. $_code = OrderStatus::INNER_ERROR;
  101. return $this->huoError($_code, OrderStatus::getMsg($_code));
  102. }
  103. /* 更新米大师游戏币 */
  104. $_mgm_data['m_balance'] = $_data['balance'] - $_product_cnt;
  105. $_mgm_data['m_gen_balance'] = $_data['gen_balance'];
  106. $_mgm_data['m_save_amt'] += $_product_cnt;
  107. $_mgm_data['m_save_sum'] = $_data['save_sum'];
  108. $_mgm_data['m_cost_sum'] = $_data['cost_sum'];
  109. $_mgm_data['m_present_sum'] = $_data['present_sum'];
  110. $_mgm_data['sum_money'] += $_order_data['amount'];
  111. $_mgm_data['total'] += $_product_cnt;
  112. $_mgm_data['remain'] += $_mgm_data['m_balance'];
  113. $_mgm_model->updateData($_mgm_data, $_mgm_data['id']);
  114. /* 插入每条记录 */
  115. // TODO: wuyonghong 2018/8/16 插入log记录
  116. /* 从订单中获取状态 */
  117. $_statues = (new Sdk())->getStatus($order_id);
  118. if (false == $_statues) {
  119. $_code = OrderStatus::ORDER_ID_ERROR;
  120. return $this->huoError($_code, OrderStatus::getMsg($_code), $_rdata);
  121. }
  122. $_code = OrderStatus::NO_ERROR;
  123. $_rdata = [
  124. 'order_id' => $order_id,
  125. 'status' => $_statues['status'],
  126. 'cp_status' => $_statues['cp_status']
  127. ];
  128. return $this->huoSuccess($_code, OrderStatus::getMsg($_code), $_rdata);
  129. }
  130. /**
  131. * @param string $app_id 应用ID
  132. * @param bool $is_sand 是否沙盒环境
  133. *
  134. * @return array
  135. */
  136. public function getBaseParam($app_id, $is_sand = false) {
  137. $_mpay_data = (new GameMini())->getMpayConf($app_id, $is_sand);
  138. $_rdata['appid'] = $_mpay_data['appid']; /* 小程序ID */
  139. $_rdata['offer_id'] = $_mpay_data['offer_id']; /* 米大师支付应用ID */
  140. $_rdata['ts'] = time();
  141. $_rdata['zone_id'] = '1';
  142. $_rdata['pf'] = 'android';
  143. $_rdata['app_secret'] = $_mpay_data['app_secret']; /* 小程序AppSecret */
  144. $_rdata['app_key'] = $_mpay_data['app_key']; /* 米大师AppKey */
  145. return $_rdata;
  146. }
  147. /**
  148. * @param int $mem_id
  149. * @param int $app_id
  150. *
  151. * @return string
  152. */
  153. public function getOpenId($mem_id, $app_id = 0) {
  154. $_open_id = (new HuoSession($mem_id, $app_id))->getOpenId();
  155. if (!empty($_open_id)) {
  156. return $_open_id;
  157. }
  158. return '';
  159. }
  160. /**
  161. * @param int $mem_id
  162. * @param int $app_id
  163. *
  164. * @return string
  165. */
  166. public function getSessionKey($mem_id, $app_id) {
  167. $_session_key = (new HuoSession($mem_id, $app_id))->getAccessToken();
  168. if (!empty($_session_key)) {
  169. return $_session_key;
  170. }
  171. return '';
  172. }
  173. /**
  174. * 开通了虚拟支付的小游戏,可以通过本接口查看某个用户的游戏币余额
  175. * https://developers.weixin.qq.com/minigame/dev/document/midas-payment/midasGetBalance.html
  176. *
  177. * @param int $mem_id 玩家ID
  178. * @param string $order_id 订单ID
  179. *
  180. * @return int|mixed
  181. */
  182. public function midasGetBalance($mem_id, $order_id = '') {
  183. $_order_data = SdkOrderCache::ins()->getInfoByOrderId($order_id);
  184. $_is_sand = true;
  185. if (PaywayConst::PAYWAY_MPAY == $_order_data['payway']) {
  186. $_is_sand = false;
  187. }
  188. $_app_id = $_order_data['app_id'];
  189. $_uri = '/cgi-bin/midas/getbalance';
  190. if ($_is_sand) {
  191. $_uri = '/cgi-bin/midas/sandbox/getbalance';
  192. }
  193. $_base_params = $this->getBaseParam($_app_id, $_is_sand);
  194. $_open_id = $this->getOpenId($mem_id, $_app_id);
  195. $_session_key = $this->getSessionKey($mem_id, $_app_id);
  196. $_params = [
  197. 'openid' => $_open_id,
  198. 'appid' => $_base_params['appid'],
  199. 'offer_id' => $_base_params['offer_id'],
  200. 'ts' => $_base_params['ts'],
  201. 'zone_id' => $_base_params['zone_id'],
  202. 'pf' => $_base_params['pf'],
  203. //'user_ip' => $_order_data['payext']['ip'],/* 用户外网 IP */
  204. ];
  205. $_mp_app_secret = $_base_params['app_secret']; /* 小程序AppSecret */
  206. $_midas_app_key = $_base_params['app_key']; /* 米大师密钥 */
  207. $_rs = MpaySign::apiPay($_uri, $_params, $_mp_app_secret, $_midas_app_key, $_session_key);
  208. if (OrderStatus::NO_ERROR == $_rs['code']) {
  209. $_mgm_log_data['mem_id'] = $mem_id;
  210. $_mgm_log_data['app_id'] = $_order_data['app_id'];
  211. $_mgm_log_data['order_id'] = $_order_data['order_id'];
  212. $_mgm_log_data['m_balance'] = $_rs['data']['balance'];
  213. $_mgm_log_data['m_gen_balance'] = $_rs['data']['gen_balance'];
  214. $_mgm_log_data['m_save_amt'] = $_rs['data']['save_amt'];
  215. $_mgm_log_data['m_save_sum'] = $_rs['data']['save_sum'];
  216. $_mgm_log_data['m_cost_sum'] = $_rs['data']['cost_sum'];
  217. $_mgm_log_data['m_present_sum'] = $_rs['data']['present_sum'];
  218. $_mgm_log_data['server_id'] = $_base_params['zone_id'];
  219. $_mgm_log_data['type'] = self::MGM_QUERY;
  220. (new MgmLogModel())->insertLog($_mgm_log_data);
  221. }
  222. return $_rs;
  223. }
  224. /**
  225. * 开通了虚拟支付的小游戏,若扣除游戏币的订单号在有效时间内,可以通过本接口取消该笔扣除游戏币的订单
  226. * https://developers.weixin.qq.com/minigame/dev/document/midas-payment/midasCancelPay.html
  227. *
  228. *
  229. * @param int $mem_id 玩家ID
  230. * @param string $order_id 订单ID
  231. *
  232. * @return array|mixed
  233. */
  234. function midasCancelPay($mem_id, $order_id) {
  235. $_order_data = SdkOrderCache::ins()->getInfoByOrderId($order_id);
  236. $_is_sand = true;
  237. if (PaywayConst::PAYWAY_MPAY == $_order_data['payway']) {
  238. $_is_sand = false;
  239. }
  240. $_app_id = $_order_data['app_id'];
  241. $_uri = '/cgi-bin/midas/cancelpay';
  242. if ($_is_sand) {
  243. $_uri = '/cgi-bin/midas/sandbox/cancelpay';
  244. }
  245. $_base_params = $this->getBaseParam($_app_id, $_is_sand);
  246. $_open_id = $this->getOpenId($mem_id);
  247. $_session_key = $this->getSessionKey($mem_id);
  248. $_params = [
  249. 'openid' => $_open_id,
  250. 'appid' => $_base_params['appid'],
  251. 'offer_id' => $_base_params['offer_id'],
  252. 'ts' => $_base_params['ts'],
  253. 'zone_id' => $_base_params['zone_id'],
  254. 'pf' => $_base_params['pf'],
  255. //'user_ip' => $_order_data['payext']['ip'],/* 用户外网 IP */
  256. 'bill_no' => $_order_data['order_id'],
  257. // 'pay_item' => $_order_data['payext']['product_name'], /* 道具名称 */
  258. ];
  259. $_mp_app_secret = $_base_params['app_secret']; /* 小程序AppSecret */
  260. $_midas_app_key = $_base_params['app_key']; /* 米大师密钥 */
  261. $_rs = MpaySign::apiPay($_uri, $_params, $_mp_app_secret, $_midas_app_key, $_session_key);
  262. if (OrderStatus::NO_ERROR == $_rs['code']) {
  263. $_mgm_log_data['mem_id'] = $mem_id;
  264. $_mgm_log_data['app_id'] = $_order_data['app_id'];
  265. $_mgm_log_data['order_id'] = $_order_data['order_id'];
  266. $_mgm_log_data['type'] = self::MGM_CANCEL;
  267. (new MgmLogModel())->insertLog($_mgm_log_data);
  268. }
  269. return $_rs;
  270. }
  271. /**
  272. * 米大师付款
  273. * 开通了虚拟支付的小游戏,可以通过本接口扣除某个用户的游戏币。
  274. * 由于可能存在接口调用超时或返回系统失败,但是游戏币实际已经扣除的情况,所以当该接口返回系统失败时,可以用相同的bill_no再次调用本接口,直到返回非系统失败为止,不会重复扣款,也可以调用取消支付接口取消本次扣款。
  275. * https://developers.weixin.qq.com/minigame/dev/document/midas-payment/midasPay.html
  276. *
  277. *
  278. * @param int $mem_id 玩家ID
  279. * @param string $order_id 订单ID
  280. *
  281. * @return array|mixed
  282. */
  283. function midasPay($mem_id, $order_id) {
  284. $_order_data = SdkOrderCache::ins()->getInfoByOrderId($order_id);
  285. $_is_sand = true;
  286. if (PaywayConst::PAYWAY_MPAY == $_order_data['payway']) {
  287. $_is_sand = false;
  288. }
  289. $_app_id = $_order_data['app_id'];
  290. $_uri = '/cgi-bin/midas/pay';
  291. if ($_is_sand) {
  292. $_uri = '/cgi-bin/midas/sandbox/pay';
  293. }
  294. $_base_params = $this->getBaseParam($_app_id, $_is_sand);
  295. $_open_id = $this->getOpenId($mem_id, $_app_id);
  296. $_session_key = $this->getSessionKey($mem_id, $_app_id);
  297. $_params = [
  298. 'openid' => $_open_id,
  299. 'appid' => $_base_params['appid'],
  300. 'offer_id' => $_base_params['offer_id'],
  301. 'ts' => $_base_params['ts'],
  302. 'zone_id' => $_base_params['zone_id'],
  303. 'pf' => $_base_params['pf'],
  304. // 'user_ip' => $_order_data['payext']['ip'],
  305. // 'pay_item' => $_order_data['payext']['product_name'],
  306. // 'app_remark' => $_order_data['payext']['remark'],
  307. 'amt' => $_order_data['product_cnt'], /* 扣除游戏币数量,不能为 0 */
  308. 'bill_no' => $_order_data['order_id'],
  309. ];
  310. $_mp_app_secret = $_base_params['app_secret']; /* 小程序AppSecret */
  311. $_midas_app_key = $_base_params['app_key']; /* 米大师密钥 */
  312. $_rs = MpaySign::apiPay($_uri, $_params, $_mp_app_secret, $_midas_app_key, $_session_key);
  313. if (OrderStatus::NO_ERROR == $_rs['code']) {
  314. $_mgm_log_data['mem_id'] = $mem_id;
  315. $_mgm_log_data['app_id'] = $_order_data['app_id'];
  316. $_mgm_log_data['order_id'] = $_rs['data']['bill_no'];
  317. $_mgm_log_data['m_balance'] = $_rs['data']['balance'];
  318. $_mgm_log_data['m_used_gen_balance'] = isset($_rs['data']['used_gen_amt'])
  319. ? $_rs['data']['used_gen_amt'] : 0;
  320. $_mgm_log_data['type'] = self::MGM_PAY;
  321. (new MgmLogModel())->insertLog($_mgm_log_data);
  322. }
  323. return $_rs;
  324. }
  325. /**
  326. * 米大师赠送
  327. * 开通了虚拟支付的小游戏,可以通过该接口赠送游戏币给某个用户。
  328. * https://developers.weixin.qq.com/minigame/dev/document/midas-payment/midasPresent.html
  329. *
  330. *
  331. * @param int $mem_id 玩家ID
  332. * @param string $order_id 订单ID
  333. * @param int $present_counts 赠送游戏币的个数,不能为0
  334. *
  335. *
  336. * 0 请求成功
  337. * -1 系统繁忙,此时请开发者稍候再试
  338. * 90009 mp_sig签名错误
  339. * 90010 用户未登录或登录态已过期
  340. * 90011 sig签名错误
  341. * 90012 订单已存在
  342. * 90017 没有调用接口的权限
  343. * 90018 参数错误
  344. *
  345. * @return array|mixed
  346. */
  347. function midasPresent($mem_id, $order_id, $present_counts) {
  348. $_order_data = SdkOrderCache::ins()->getInfoByOrderId($order_id);
  349. $_is_sand = true;
  350. if (PaywayConst::PAYWAY_MPAY == $_order_data['payway']) {
  351. $_is_sand = false;
  352. }
  353. $_app_id = $_order_data['app_id'];
  354. $_uri = '/cgi-bin/midas/present';
  355. if ($_is_sand) {
  356. $_uri = '/cgi-bin/midas/sandbox/present';
  357. }
  358. $_base_params = $this->getBaseParam($_app_id, $_is_sand);
  359. $_open_id = $this->getOpenId($mem_id);
  360. $_session_key = $this->getSessionKey($mem_id);
  361. $_params = [
  362. 'openid' => $_open_id,
  363. 'appid' => $_base_params['appid'],
  364. 'offer_id' => $_base_params['offer_id'],
  365. 'ts' => $_base_params['ts'],
  366. 'zone_id' => $_base_params['zone_id'],
  367. 'pf' => $_base_params['pf'],
  368. // 'user_ip' => $_order_data['payext']['ip'],
  369. 'bill_no' => $_order_data['order_id'],
  370. 'present_counts' => $present_counts, /* 赠送游戏币的个数,不能为0 */
  371. ];
  372. $_mp_app_secret = $_base_params['app_secret']; /* 小程序AppSecret */
  373. $_midas_app_key = $_base_params['app_key']; /* 米大师密钥 */
  374. $_rs = MpaySign::apiPay($_uri, $_params, $_mp_app_secret, $_midas_app_key, $_session_key);
  375. if (OrderStatus::NO_ERROR == $_rs['code']) {
  376. $_mgm_log_data['mem_id'] = $mem_id;
  377. $_mgm_log_data['app_id'] = $_order_data['app_id'];
  378. $_mgm_log_data['order_id'] = $_rs['data']['bill_no'];
  379. $_mgm_log_data['m_balance'] = $_rs['data']['balance'];
  380. $_mgm_log_data['type'] = self::MGM_PRESENT;
  381. (new MgmLogModel())->insertLog($_mgm_log_data);
  382. }
  383. return $_rs;
  384. }
  385. }