正在显示
61 个修改的文件
包含
4815 行增加
和
0 行删除
addons/epay/.addonrc
0 → 100644
| 1 | +{"files":["application\\admin\\controller\\Epay.php","public\\assets\\addons\\epay\\css\\common.css","public\\assets\\addons\\epay\\images\\alipay.png","public\\assets\\addons\\epay\\images\\expired.png","public\\assets\\addons\\epay\\images\\logo-alipay.png","public\\assets\\addons\\epay\\images\\logo-wechat.png","public\\assets\\addons\\epay\\images\\paid.png","public\\assets\\addons\\epay\\images\\scan.png","public\\assets\\addons\\epay\\images\\screenshot-alipay.png","public\\assets\\addons\\epay\\images\\screenshot-wechat.png","public\\assets\\addons\\epay\\images\\wechat.png","public\\assets\\addons\\epay\\js\\common.js","public\\assets\\addons\\epay\\js\\jquery.qrcode.min.js","public\\assets\\addons\\epay\\less\\common.less"],"license":"regular","licenseto":"15629","licensekey":"ArY9zvN8lSfKu4Pw cYmPbH3AGCnkWBKBpdP6OA==","domains":["cardverification.com"],"licensecodes":[],"validations":["867a7ee97b89c3a1f31235c70da23ecf"]} |
addons/epay/Epay.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace addons\epay; | ||
| 4 | + | ||
| 5 | +use think\Addons; | ||
| 6 | +use think\Config; | ||
| 7 | +use think\Loader; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 微信支付宝整合插件 | ||
| 11 | + */ | ||
| 12 | +class Epay extends Addons | ||
| 13 | +{ | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 插件安装方法 | ||
| 17 | + * @return bool | ||
| 18 | + */ | ||
| 19 | + public function install() | ||
| 20 | + { | ||
| 21 | + | ||
| 22 | + return true; | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 插件卸载方法 | ||
| 27 | + * @return bool | ||
| 28 | + */ | ||
| 29 | + public function uninstall() | ||
| 30 | + { | ||
| 31 | + | ||
| 32 | + return true; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * 插件启用方法 | ||
| 37 | + * @return bool | ||
| 38 | + */ | ||
| 39 | + public function enable() | ||
| 40 | + { | ||
| 41 | + | ||
| 42 | + return true; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * 插件禁用方法 | ||
| 47 | + * @return bool | ||
| 48 | + */ | ||
| 49 | + public function disable() | ||
| 50 | + { | ||
| 51 | + | ||
| 52 | + return true; | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + /** | ||
| 56 | + * 添加命名空间 | ||
| 57 | + */ | ||
| 58 | + public function appInit() | ||
| 59 | + { | ||
| 60 | + //添加命名空间 | ||
| 61 | + if (!class_exists('\Yansongda\Pay\Pay')) { | ||
| 62 | + Loader::addNamespace('Yansongda\Pay', ADDON_PATH . 'epay' . DS . 'library' . DS . 'Yansongda' . DS . 'Pay' . DS); | ||
| 63 | + } | ||
| 64 | + if (!class_exists('\Yansongda\Supports\Logger')) { | ||
| 65 | + Loader::addNamespace('Yansongda\Supports', ADDON_PATH . 'epay' . DS . 'library' . DS . 'Yansongda' . DS . 'Supports' . DS); | ||
| 66 | + } | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | +} |
addons/epay/certs/alipayCertPublicKey.crt
0 → 100644
addons/epay/certs/alipayRootCert.crt
0 → 100644
addons/epay/certs/apiclient_cert.pem
0 → 100644
addons/epay/certs/apiclient_key.pem
0 → 100644
addons/epay/certs/appCertPublicKey.crt
0 → 100644
addons/epay/config.html
0 → 100644
| 1 | +<form id="config-form" class="edit-form form-horizontal" role="form" data-toggle="validator" method="POST" action=""> | ||
| 2 | + | ||
| 3 | + <div class="panel panel-default panel-intro"> | ||
| 4 | + <div class="panel-heading"> | ||
| 5 | + <ul class="nav nav-tabs nav-group"> | ||
| 6 | + <li class="active"><a href="#wechat" data-toggle="tab">微信支付</a></li> | ||
| 7 | + <li><a href="#alipay" data-toggle="tab">支付宝</a></li> | ||
| 8 | + </ul> | ||
| 9 | + </div> | ||
| 10 | + | ||
| 11 | + <div class="panel-body"> | ||
| 12 | + <div id="myTabContent" class="tab-content"> | ||
| 13 | + {foreach $addon.config as $item} | ||
| 14 | + {if $item.name=='wechat'} | ||
| 15 | + <div class="tab-pane fade active in" id="wechat"> | ||
| 16 | + <table class="table table-striped table-config"> | ||
| 17 | + <tbody> | ||
| 18 | + <tr> | ||
| 19 | + <td width="15%">APP的app_id</td> | ||
| 20 | + <td> | ||
| 21 | + <div class="row"> | ||
| 22 | + <div class="col-sm-8 col-xs-12"> | ||
| 23 | + <input type="text" name="row[wechat][appid]" value="{$item.value.appid|default=''}" class="form-control" data-rule="" data-tip="APP应用中支付时使用"/> | ||
| 24 | + </div> | ||
| 25 | + <div class="col-sm-4"></div> | ||
| 26 | + </div> | ||
| 27 | + </td> | ||
| 28 | + </tr> | ||
| 29 | + <tr> | ||
| 30 | + <td>公众号的app_id</td> | ||
| 31 | + <td> | ||
| 32 | + <div class="row"> | ||
| 33 | + <div class="col-sm-8 col-xs-12"> | ||
| 34 | + <input type="text" name="row[wechat][app_id]" value="{$item.value.app_id|default=''}" class="form-control" data-rule="" data-tip="公众号中支付时使用"/> | ||
| 35 | + </div> | ||
| 36 | + <div class="col-sm-4"></div> | ||
| 37 | + </div> | ||
| 38 | + </td> | ||
| 39 | + </tr> | ||
| 40 | + <tr> | ||
| 41 | + <td>公众号的app_secret</td> | ||
| 42 | + <td> | ||
| 43 | + <div class="row"> | ||
| 44 | + <div class="col-sm-8 col-xs-12"> | ||
| 45 | + <input type="text" name="row[wechat][app_secret]" value="{$item.value.app_secret|default=''}" class="form-control" data-rule="" data-tip="仅在需要获取Openid时使用"/> | ||
| 46 | + </div> | ||
| 47 | + <div class="col-sm-4"></div> | ||
| 48 | + </div> | ||
| 49 | + </td> | ||
| 50 | + </tr> | ||
| 51 | + <tr> | ||
| 52 | + <td>小程序的app_id</td> | ||
| 53 | + <td> | ||
| 54 | + <div class="row"> | ||
| 55 | + <div class="col-sm-8 col-xs-12"> | ||
| 56 | + <input type="text" name="row[wechat][miniapp_id]" value="{$item.value.miniapp_id|default=''}" class="form-control" data-rule="" data-tip="仅在小程序支付时使用"/> | ||
| 57 | + </div> | ||
| 58 | + <div class="col-sm-4"></div> | ||
| 59 | + </div> | ||
| 60 | + </td> | ||
| 61 | + </tr> | ||
| 62 | + <tr> | ||
| 63 | + <td>微信支付商户号ID</td> | ||
| 64 | + <td> | ||
| 65 | + <div class="row"> | ||
| 66 | + <div class="col-sm-8 col-xs-12"> | ||
| 67 | + <input type="text" name="row[wechat][mch_id]" value="{$item.value.mch_id|default=''}" class="form-control" data-rule="" data-tip=""/> | ||
| 68 | + </div> | ||
| 69 | + <div class="col-sm-4"></div> | ||
| 70 | + </div> | ||
| 71 | + </td> | ||
| 72 | + </tr> | ||
| 73 | + <tr> | ||
| 74 | + <td>微信支付商户的密钥</td> | ||
| 75 | + <td> | ||
| 76 | + <div class="row"> | ||
| 77 | + <div class="col-sm-8 col-xs-12"> | ||
| 78 | + <input type="text" name="row[wechat][key]" value="{$item.value.key|default=''}" class="form-control" data-rule="" data-tip=""/> | ||
| 79 | + </div> | ||
| 80 | + <div class="col-sm-4"></div> | ||
| 81 | + </div> | ||
| 82 | + </td> | ||
| 83 | + </tr> | ||
| 84 | + <tr> | ||
| 85 | + <td>支付模式</td> | ||
| 86 | + <td> | ||
| 87 | + <div class="row"> | ||
| 88 | + <div class="col-sm-8 col-xs-12"> | ||
| 89 | + {:Form::radios('row[wechat][mode]',['normal'=>'正式环境','dev'=>'沙箱环境','service'=>'服务商模式'],$item.value.mode??'normal')} | ||
| 90 | + </div> | ||
| 91 | + <div class="col-sm-4"></div> | ||
| 92 | + </div> | ||
| 93 | + </td> | ||
| 94 | + </tr> | ||
| 95 | + <tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}"> | ||
| 96 | + <td>子商户商户号ID</td> | ||
| 97 | + <td> | ||
| 98 | + <div class="row"> | ||
| 99 | + <div class="col-sm-8 col-xs-12"> | ||
| 100 | + <input type="text" name="row[wechat][sub_mch_id]" value="{$item.value.sub_mch_id|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/> | ||
| 101 | + </div> | ||
| 102 | + <div class="col-sm-4"></div> | ||
| 103 | + </div> | ||
| 104 | + </td> | ||
| 105 | + </tr> | ||
| 106 | + <tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}"> | ||
| 107 | + <td>子商户APP的app_id</td> | ||
| 108 | + <td> | ||
| 109 | + <div class="row"> | ||
| 110 | + <div class="col-sm-8 col-xs-12"> | ||
| 111 | + <input type="text" name="row[wechat][sub_appid]" value="{$item.value.sub_appid|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/> | ||
| 112 | + </div> | ||
| 113 | + <div class="col-sm-4"></div> | ||
| 114 | + </div> | ||
| 115 | + </td> | ||
| 116 | + </tr> | ||
| 117 | + <tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}"> | ||
| 118 | + <td>子商户公众号的app_id</td> | ||
| 119 | + <td> | ||
| 120 | + <div class="row"> | ||
| 121 | + <div class="col-sm-8 col-xs-12"> | ||
| 122 | + <input type="text" name="row[wechat][sub_app_id]" value="{$item.value.sub_app_id|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/> | ||
| 123 | + </div> | ||
| 124 | + <div class="col-sm-4"></div> | ||
| 125 | + </div> | ||
| 126 | + </td> | ||
| 127 | + </tr> | ||
| 128 | + <tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}"> | ||
| 129 | + <td>子商户小程序的app_id</td> | ||
| 130 | + <td> | ||
| 131 | + <div class="row"> | ||
| 132 | + <div class="col-sm-8 col-xs-12"> | ||
| 133 | + <input type="text" name="row[wechat][sub_miniapp_id]" value="{$item.value.sub_miniapp_id|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/> | ||
| 134 | + </div> | ||
| 135 | + <div class="col-sm-4"></div> | ||
| 136 | + </div> | ||
| 137 | + </td> | ||
| 138 | + </tr> | ||
| 139 | + <tr> | ||
| 140 | + <td>回调通知地址</td> | ||
| 141 | + <td> | ||
| 142 | + <div class="row"> | ||
| 143 | + <div class="col-sm-8 col-xs-12"> | ||
| 144 | + <input type="text" name="row[wechat][notify_url]" value="{$item.value.notify_url|default=''}" class="form-control" data-rule="" data-tip="请勿随意修改,实际以逻辑代码中请求的为准"/> | ||
| 145 | + </div> | ||
| 146 | + <div class="col-sm-4"></div> | ||
| 147 | + </div> | ||
| 148 | + </td> | ||
| 149 | + </tr> | ||
| 150 | + <tr> | ||
| 151 | + <td>微信支付API证书cert</td> | ||
| 152 | + <td> | ||
| 153 | + <div class="row"> | ||
| 154 | + <div class="col-sm-8 col-xs-12"> | ||
| 155 | + <div class="input-group"> | ||
| 156 | + <input id="c-cert_client" class="form-control" size="50" name="row[wechat][cert_client]" type="text" value="{$item.value.cert_client|htmlentities}" data-tip="可选, 仅在退款、红包等情况时需要用到"> | ||
| 157 | + <div class="input-group-addon no-border no-padding"> | ||
| 158 | + <span><button type="button" id="faupload-cert_client" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"cert_client"}' data-mimetype="pem" data-input-id="c-cert_client" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span> | ||
| 159 | + </div> | ||
| 160 | + <span class="msg-box n-right" for="c-cert_client"></span> | ||
| 161 | + </div> | ||
| 162 | + <div style="margin-top:5px;"><a href="https://pay.weixin.qq.com" target="_blank"><i class="fa fa-question-circle"></i> 如何获取微信支付API证书?</a></div> | ||
| 163 | + </div> | ||
| 164 | + <div class="col-sm-4"></div> | ||
| 165 | + </div> | ||
| 166 | + </td> | ||
| 167 | + </tr> | ||
| 168 | + <tr> | ||
| 169 | + <td>微信支付API证书key</td> | ||
| 170 | + <td> | ||
| 171 | + <div class="row"> | ||
| 172 | + <div class="col-sm-8 col-xs-12"> | ||
| 173 | + <div class="input-group"> | ||
| 174 | + <input id="c-cert_key" class="form-control" size="50" name="row[wechat][cert_key]" type="text" value="{$item.value.cert_key|htmlentities}" data-tip="可选, 仅在退款、红包等情况时需要用到"> | ||
| 175 | + <div class="input-group-addon no-border no-padding"> | ||
| 176 | + <span><button type="button" id="faupload-cert_key" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"cert_key"}' data-mimetype="pem" data-input-id="c-cert_key" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span> | ||
| 177 | + </div> | ||
| 178 | + <span class="msg-box n-right" for="c-cert_key"></span> | ||
| 179 | + </div> | ||
| 180 | + <div style="margin-top:5px;"><a href="https://pay.weixin.qq.com" target="_blank"><i class="fa fa-question-circle"></i> 如何获取微信支付API证书?</a></div> | ||
| 181 | + </div> | ||
| 182 | + <div class="col-sm-4"></div> | ||
| 183 | + </div> | ||
| 184 | + </td> | ||
| 185 | + </tr> | ||
| 186 | + | ||
| 187 | + <tr> | ||
| 188 | + <td>记录日志</td> | ||
| 189 | + <td> | ||
| 190 | + <div class="row"> | ||
| 191 | + <div class="col-sm-8 col-xs-12"> | ||
| 192 | + {:Form::radios('row[wechat][log]',['1'=>'开启','0'=>'关闭'],$item.value.log)} | ||
| 193 | + </div> | ||
| 194 | + <div class="col-sm-4"></div> | ||
| 195 | + </div> | ||
| 196 | + </td> | ||
| 197 | + </tr> | ||
| 198 | + </tbody> | ||
| 199 | + </table> | ||
| 200 | + </div> | ||
| 201 | + {elseif $item.name=='alipay'} | ||
| 202 | + <div class="tab-pane fade" id="alipay"> | ||
| 203 | + <table class="table table-striped table-config"> | ||
| 204 | + <tbody> | ||
| 205 | + <tr> | ||
| 206 | + <td>支付模式</td> | ||
| 207 | + <td> | ||
| 208 | + <div class="row"> | ||
| 209 | + <div class="col-sm-12 col-xs-12"> | ||
| 210 | + {:Form::radios('row[alipay][mode]',['normal'=>'正式环境','dev'=>'沙箱环境'],$item.value.mode??'normal')} | ||
| 211 | + | ||
| 212 | + <div style="margin-top:5px;" data-mode="dev" class="text-muted {if ($item.value.mode??'')==='normal'}hidden{/if}"> | ||
| 213 | + <i class="fa fa-info-circle"></i> 如果使用沙箱环境,务必使用沙箱的app_id和沙箱证书,以及使用沙箱账号进行测试。<br> | ||
| 214 | + 沙箱环境:<a href="https://openhome.alipay.com/develop/sandbox/app" target="_blank">https://openhome.alipay.com/develop/sandbox/app</a> | ||
| 215 | + </div> | ||
| 216 | + </div> | ||
| 217 | + </div> | ||
| 218 | + </td> | ||
| 219 | + </tr> | ||
| 220 | + <tr> | ||
| 221 | + <td width="15%">应用ID(app_id)</td> | ||
| 222 | + <td> | ||
| 223 | + <div class="row"> | ||
| 224 | + <div class="col-sm-8 col-xs-12"> | ||
| 225 | + <input type="text" name="row[alipay][app_id]" value="{$item.value.app_id|default=''}" class="form-control" data-rule="" data-tip=""/> | ||
| 226 | + </div> | ||
| 227 | + <div class="col-sm-4"></div> | ||
| 228 | + </div> | ||
| 229 | + </td> | ||
| 230 | + </tr> | ||
| 231 | + <tr> | ||
| 232 | + <td>回调通知地址</td> | ||
| 233 | + <td> | ||
| 234 | + <div class="row"> | ||
| 235 | + <div class="col-sm-8 col-xs-12"> | ||
| 236 | + <input type="text" name="row[alipay][notify_url]" value="{$item.value.notify_url|default=''}" class="form-control" data-rule="" data-tip="请勿随意修改,实际以逻辑代码中请求的为准"/> | ||
| 237 | + </div> | ||
| 238 | + <div class="col-sm-4"></div> | ||
| 239 | + </div> | ||
| 240 | + </td> | ||
| 241 | + </tr> | ||
| 242 | + <tr> | ||
| 243 | + <td>支付跳转地址</td> | ||
| 244 | + <td> | ||
| 245 | + <div class="row"> | ||
| 246 | + <div class="col-sm-8 col-xs-12"> | ||
| 247 | + <input type="text" name="row[alipay][return_url]" value="{$item.value.return_url|default=''}" class="form-control" data-rule="" data-tip="请勿随意修改,实际以逻辑代码中请求的为准"/> | ||
| 248 | + </div> | ||
| 249 | + <div class="col-sm-4"></div> | ||
| 250 | + </div> | ||
| 251 | + </td> | ||
| 252 | + </tr> | ||
| 253 | + <tr> | ||
| 254 | + <td>应用私钥(private_key)</td> | ||
| 255 | + <td> | ||
| 256 | + <div class="row"> | ||
| 257 | + <div class="col-sm-8 col-xs-12"> | ||
| 258 | + <input type="text" name="row[alipay][private_key]" value="{$item.value.private_key|default=''}" class="form-control" data-rule=""/> | ||
| 259 | + <div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/helpcenter/207/201602469554" target="_blank"><i class="fa fa-question-circle"></i> 如何获取应用私钥?</a></div> | ||
| 260 | + </div> | ||
| 261 | + <div class="col-sm-4"></div> | ||
| 262 | + </div> | ||
| 263 | + </td> | ||
| 264 | + </tr> | ||
| 265 | + <tr> | ||
| 266 | + <td>签名方式</td> | ||
| 267 | + <td> | ||
| 268 | + <div> | ||
| 269 | + <div class="radio"> | ||
| 270 | + <label for="row[alipay][signtype]-publickey"><input id="row[alipay][signtype]-publickey" name="row[alipay][signtype]" {if isset($item.value.signtype)&&$item.value.signtype=='publickey'}checked{/if} type="radio" value="publickey"> 公钥</label> | ||
| 271 | + <label for="row[alipay][signtype]-cert"><input id="row[alipay][signtype]-cert" {if isset($item.value.signtype)&&$item.value.signtype=='cert'}checked{/if} name="row[alipay][signtype]" type="radio" value="cert"> 公钥证书</label> | ||
| 272 | + </div> | ||
| 273 | + </div> | ||
| 274 | + <div style="margin-top:5px;" class="text-muted"> | ||
| 275 | + <i class="fa fa-info-circle"></i> 如果要使用转账、提现功能,则必须使用公钥证书 | ||
| 276 | + </div> | ||
| 277 | + </td> | ||
| 278 | + </tr> | ||
| 279 | + <tr> | ||
| 280 | + <td>支付宝公钥路径(alipay_public_key)</td> | ||
| 281 | + <td> | ||
| 282 | + <div class="row"> | ||
| 283 | + <div class="col-sm-8 col-xs-12"> | ||
| 284 | + <div class="input-group"> | ||
| 285 | + <input id="c-ali_public_key" class="form-control" size="50" name="row[alipay][ali_public_key]" type="text" value="{$item.value.ali_public_key|default=''|htmlentities}" placeholder="公钥请直接粘贴,公钥证书请点击右侧的上传"> | ||
| 286 | + <div class="input-group-addon no-border no-padding"> | ||
| 287 | + <span><button type="button" id="faupload-ali_public_key" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"ali_public_key"}' data-mimetype="crt" data-input-id="c-ali_public_key" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span> | ||
| 288 | + </div> | ||
| 289 | + <span class="msg-box n-right" for="c-ali_public_key"></span> | ||
| 290 | + </div> | ||
| 291 | + <div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/helpcenter/271/201602474998" target="_blank"><i class="fa fa-question-circle"></i> 如何获取支付宝公钥证书?</a></div> | ||
| 292 | + </div> | ||
| 293 | + <div class="col-sm-4"></div> | ||
| 294 | + </div> | ||
| 295 | + </td> | ||
| 296 | + </tr> | ||
| 297 | + <tr> | ||
| 298 | + <td>应用公钥证书路径(app_cert_public_key)</td> | ||
| 299 | + <td> | ||
| 300 | + <div class="row"> | ||
| 301 | + <div class="col-sm-8 col-xs-12"> | ||
| 302 | + <div class="input-group"> | ||
| 303 | + <input id="c-app_cert_public_key" class="form-control" size="50" name="row[alipay][app_cert_public_key]" type="text" value="{$item.value.app_cert_public_key|default=''|htmlentities}"> | ||
| 304 | + <div class="input-group-addon no-border no-padding"> | ||
| 305 | + <span><button type="button" id="faupload-app_cert_public_key" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"app_cert_public_key"}' data-mimetype="crt" data-input-id="c-app_cert_public_key" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span> | ||
| 306 | + </div> | ||
| 307 | + <span class="msg-box n-right" for="c-app_cert_public_key"></span> | ||
| 308 | + </div> | ||
| 309 | + <div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/helpcenter/271/201602474998" target="_blank"><i class="fa fa-question-circle"></i> 如何获取应用公钥证书?</a></div> | ||
| 310 | + </div> | ||
| 311 | + <div class="col-sm-4"></div> | ||
| 312 | + </div> | ||
| 313 | + </td> | ||
| 314 | + </tr> | ||
| 315 | + <tr> | ||
| 316 | + <td>支付宝根证书路径(alipay_root_cert)</td> | ||
| 317 | + <td> | ||
| 318 | + <div class="row"> | ||
| 319 | + <div class="col-sm-8 col-xs-12"> | ||
| 320 | + <div class="input-group"> | ||
| 321 | + <input id="c-alipay_root_cert" class="form-control" size="50" name="row[alipay][alipay_root_cert]" type="text" value="{$item.value.alipay_root_cert|default=''|htmlentities}"> | ||
| 322 | + <div class="input-group-addon no-border no-padding"> | ||
| 323 | + <span><button type="button" id="faupload-alipay_root_cert" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"alipay_root_cert"}' data-mimetype="crt" data-input-id="c-alipay_root_cert" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span> | ||
| 324 | + </div> | ||
| 325 | + <span class="msg-box n-right" for="c-alipay_root_cert"></span> | ||
| 326 | + </div> | ||
| 327 | + <div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/helpcenter/271/201602474998" target="_blank"><i class="fa fa-question-circle"></i> 如何获取支付宝证书?</a></div> | ||
| 328 | + </div> | ||
| 329 | + <div class="col-sm-4"></div> | ||
| 330 | + </div> | ||
| 331 | + </td> | ||
| 332 | + </tr> | ||
| 333 | + | ||
| 334 | + <tr> | ||
| 335 | + <td>记录日志</td> | ||
| 336 | + <td> | ||
| 337 | + <div class="row"> | ||
| 338 | + <div class="col-sm-8 col-xs-12"> | ||
| 339 | + {:Form::radios('row[alipay][log]',['1'=>'开启','0'=>'关闭'],$item.value.log)} | ||
| 340 | + </div> | ||
| 341 | + <div class="col-sm-4"></div> | ||
| 342 | + </div> | ||
| 343 | + </td> | ||
| 344 | + </tr> | ||
| 345 | + | ||
| 346 | + <tr> | ||
| 347 | + <td>PC端使用扫码支付</td> | ||
| 348 | + <td> | ||
| 349 | + <div class="row"> | ||
| 350 | + <div class="col-sm-8 col-xs-12"> | ||
| 351 | + {:Form::radios('row[alipay][scanpay]',['1'=>'开启','0'=>'关闭'],$item.value.scanpay??0)} | ||
| 352 | + </div> | ||
| 353 | + <div class="col-sm-4"></div> | ||
| 354 | + </div> | ||
| 355 | + </td> | ||
| 356 | + </tr> | ||
| 357 | + </tbody> | ||
| 358 | + </table> | ||
| 359 | + </div> | ||
| 360 | + {/if} | ||
| 361 | + {/foreach} | ||
| 362 | + <div class="form-group layer-footer"> | ||
| 363 | + <label class="control-label col-xs-12 col-sm-2"></label> | ||
| 364 | + <div class="col-xs-12 col-sm-8"> | ||
| 365 | + <button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button> | ||
| 366 | + <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button> | ||
| 367 | + </div> | ||
| 368 | + </div> | ||
| 369 | + </div> | ||
| 370 | + </div> | ||
| 371 | + </div> | ||
| 372 | +</form> | ||
| 373 | +<script> | ||
| 374 | + document.querySelectorAll("input[name='row[wechat][mode]']").forEach(function (i, j) { | ||
| 375 | + i.addEventListener("click", function () { | ||
| 376 | + document.querySelectorAll("#wechat table tr[data-type]").forEach(function (m, n) { | ||
| 377 | + m.classList.add("hidden"); | ||
| 378 | + }); | ||
| 379 | + document.querySelectorAll("#wechat table tr[data-type='" + this.value + "']").forEach(function (m, n) { | ||
| 380 | + m.classList.remove("hidden"); | ||
| 381 | + }); | ||
| 382 | + }); | ||
| 383 | + }); | ||
| 384 | + document.querySelectorAll("input[name='row[alipay][mode]']").forEach(function (i, j) { | ||
| 385 | + i.addEventListener("click", function () { | ||
| 386 | + document.querySelectorAll("#alipay [data-mode]").forEach(function (m, n) { | ||
| 387 | + m.classList.add("hidden"); | ||
| 388 | + }); | ||
| 389 | + document.querySelectorAll("#alipay [data-mode='" + this.value + "']").forEach(function (m, n) { | ||
| 390 | + m.classList.remove("hidden"); | ||
| 391 | + }); | ||
| 392 | + }); | ||
| 393 | + }); | ||
| 394 | +</script> |
addons/epay/config.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +return [ | ||
| 4 | + [ | ||
| 5 | + 'name' => 'wechat', | ||
| 6 | + 'title' => '微信', | ||
| 7 | + 'type' => 'array', | ||
| 8 | + 'content' => [], | ||
| 9 | + 'value' => [ | ||
| 10 | + 'appid' => '', | ||
| 11 | + 'app_id' => '', | ||
| 12 | + 'app_secret' => '', | ||
| 13 | + 'miniapp_id' => '', | ||
| 14 | + 'mch_id' => '', | ||
| 15 | + 'key' => '', | ||
| 16 | + 'mode' => 'normal', | ||
| 17 | + 'sub_mch_id' => '', | ||
| 18 | + 'sub_appid' => '', | ||
| 19 | + 'sub_app_id' => '', | ||
| 20 | + 'sub_miniapp_id' => '', | ||
| 21 | + 'notify_url' => '/addons/epay/api/notifyx/type/wechat', | ||
| 22 | + 'cert_client' => '/addons/epay/certs/apiclient_cert.pem', | ||
| 23 | + 'cert_key' => '/addons/epay/certs/apiclient_key.pem', | ||
| 24 | + 'log' => '1', | ||
| 25 | + ], | ||
| 26 | + 'rule' => '', | ||
| 27 | + 'msg' => '', | ||
| 28 | + 'tip' => '微信参数配置', | ||
| 29 | + 'ok' => '', | ||
| 30 | + 'extend' => '', | ||
| 31 | + ], | ||
| 32 | + [ | ||
| 33 | + 'name' => 'alipay', | ||
| 34 | + 'title' => '支付宝', | ||
| 35 | + 'type' => 'array', | ||
| 36 | + 'content' => [], | ||
| 37 | + 'value' => [ | ||
| 38 | + 'app_id' => '', | ||
| 39 | + 'mode' => 'normal', | ||
| 40 | + 'notify_url' => '/addons/epay/api/notifyx/type/alipay', | ||
| 41 | + 'return_url' => '/addons/epay/api/returnx/type/alipay', | ||
| 42 | + 'private_key' => '', | ||
| 43 | + 'ali_public_key' => '', | ||
| 44 | + 'app_cert_public_key' => '', | ||
| 45 | + 'alipay_root_cert' => '', | ||
| 46 | + 'log' => '1', | ||
| 47 | + 'scanpay' => '0', | ||
| 48 | + ], | ||
| 49 | + 'rule' => 'required', | ||
| 50 | + 'msg' => '', | ||
| 51 | + 'tip' => '支付宝参数配置', | ||
| 52 | + 'ok' => '', | ||
| 53 | + 'extend' => '', | ||
| 54 | + ] | ||
| 55 | +]; |
addons/epay/controller/Api.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace addons\epay\controller; | ||
| 4 | + | ||
| 5 | +use addons\epay\library\Service; | ||
| 6 | +use addons\epay\library\Wechat; | ||
| 7 | +use addons\third\model\Third; | ||
| 8 | +use app\common\library\Auth; | ||
| 9 | +use think\addons\Controller; | ||
| 10 | +use think\Response; | ||
| 11 | +use think\Session; | ||
| 12 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 13 | +use Yansongda\Pay\Pay; | ||
| 14 | + | ||
| 15 | +/** | ||
| 16 | + * API接口控制器 | ||
| 17 | + * | ||
| 18 | + * @package addons\epay\controller | ||
| 19 | + */ | ||
| 20 | +class Api extends Controller | ||
| 21 | +{ | ||
| 22 | + | ||
| 23 | + protected $layout = 'default'; | ||
| 24 | + protected $config = []; | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 默认方法 | ||
| 28 | + */ | ||
| 29 | + public function index() | ||
| 30 | + { | ||
| 31 | + return; | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * 外部提交 | ||
| 36 | + */ | ||
| 37 | + public function submit() | ||
| 38 | + { | ||
| 39 | + $this->request->filter('trim'); | ||
| 40 | + $out_trade_no = $this->request->request("out_trade_no"); | ||
| 41 | + $title = $this->request->request("title"); | ||
| 42 | + $amount = $this->request->request('amount'); | ||
| 43 | + $type = $this->request->request('type'); | ||
| 44 | + $method = $this->request->request('method', 'web'); | ||
| 45 | + $openid = $this->request->request('openid', ''); | ||
| 46 | + $auth_code = $this->request->request('auth_code', ''); | ||
| 47 | + $notifyurl = $this->request->request('notifyurl', ''); | ||
| 48 | + $returnurl = $this->request->request('returnurl', ''); | ||
| 49 | + | ||
| 50 | + if (!$amount || $amount < 0) { | ||
| 51 | + $this->error("支付金额必须大于0"); | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + if (!$type || !in_array($type, ['alipay', 'wechat'])) { | ||
| 55 | + $this->error("支付类型错误"); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + $params = [ | ||
| 59 | + 'type' => $type, | ||
| 60 | + 'out_trade_no' => $out_trade_no, | ||
| 61 | + 'title' => $title, | ||
| 62 | + 'amount' => $amount, | ||
| 63 | + 'method' => $method, | ||
| 64 | + 'openid' => $openid, | ||
| 65 | + 'auth_code' => $auth_code, | ||
| 66 | + 'notifyurl' => $notifyurl, | ||
| 67 | + 'returnurl' => $returnurl, | ||
| 68 | + ]; | ||
| 69 | + return Service::submitOrder($params); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * 微信支付(公众号支付&PC扫码支付) | ||
| 74 | + * @return string | ||
| 75 | + */ | ||
| 76 | + public function wechat() | ||
| 77 | + { | ||
| 78 | + $config = Service::getConfig('wechat'); | ||
| 79 | + | ||
| 80 | + $isWechat = stripos($this->request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false; | ||
| 81 | + $isMobile = $this->request->isMobile(); | ||
| 82 | + $this->view->assign("isWechat", $isWechat); | ||
| 83 | + $this->view->assign("isMobile", $isMobile); | ||
| 84 | + | ||
| 85 | + //发起PC支付(Scan支付)(PC扫码模式) | ||
| 86 | + if ($this->request->isAjax()) { | ||
| 87 | + $pay = Pay::wechat($config); | ||
| 88 | + $orderid = $this->request->post("orderid"); | ||
| 89 | + try { | ||
| 90 | + $result = $pay->find($orderid, 'scan'); | ||
| 91 | + if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') { | ||
| 92 | + $this->success("", "", ['status' => $result['trade_state']]); | ||
| 93 | + } else { | ||
| 94 | + $this->error("查询失败"); | ||
| 95 | + } | ||
| 96 | + } catch (GatewayException $e) { | ||
| 97 | + $this->error("查询失败"); | ||
| 98 | + } | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + $orderData = Session::get("wechatorderdata"); | ||
| 102 | + if (!$orderData) { | ||
| 103 | + $this->error("请求参数错误"); | ||
| 104 | + } | ||
| 105 | + if ($isWechat && $isMobile) { | ||
| 106 | + //发起公众号(jsapi支付),openid必须 | ||
| 107 | + | ||
| 108 | + //如果没有openid,则自动去获取openid | ||
| 109 | + if (!isset($orderData['openid']) || !$orderData['openid']) { | ||
| 110 | + $orderData['openid'] = Service::getOpenid(); | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + $orderData['method'] = 'mp'; | ||
| 114 | + $type = 'jsapi'; | ||
| 115 | + $payData = Service::submitOrder($orderData); | ||
| 116 | + if (!isset($payData['paySign'])) { | ||
| 117 | + $this->error("创建订单失败,请返回重试", ""); | ||
| 118 | + } | ||
| 119 | + } else { | ||
| 120 | + $orderData['method'] = 'scan'; | ||
| 121 | + $type = 'pc'; | ||
| 122 | + $payData = Service::submitOrder($orderData); | ||
| 123 | + if (!isset($payData['code_url'])) { | ||
| 124 | + $this->error("创建订单失败,请返回重试", ""); | ||
| 125 | + } | ||
| 126 | + } | ||
| 127 | + $this->view->assign("orderData", $orderData); | ||
| 128 | + $this->view->assign("payData", $payData); | ||
| 129 | + $this->view->assign("type", $type); | ||
| 130 | + | ||
| 131 | + $this->view->assign("title", "微信支付"); | ||
| 132 | + return $this->view->fetch(); | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + /** | ||
| 136 | + * 支付宝支付(PC扫码支付) | ||
| 137 | + * @return string | ||
| 138 | + */ | ||
| 139 | + public function alipay() | ||
| 140 | + { | ||
| 141 | + $config = Service::getConfig('alipay'); | ||
| 142 | + | ||
| 143 | + $isWechat = stripos($this->request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false; | ||
| 144 | + $isMobile = $this->request->isMobile(); | ||
| 145 | + $this->view->assign("isWechat", $isWechat); | ||
| 146 | + $this->view->assign("isMobile", $isMobile); | ||
| 147 | + | ||
| 148 | + if ($this->request->isAjax()) { | ||
| 149 | + $orderid = $this->request->post("orderid"); | ||
| 150 | + $pay = Pay::alipay($config); | ||
| 151 | + try { | ||
| 152 | + $result = $pay->find($orderid, 'scan'); | ||
| 153 | + if ($result['code'] == '10000' && $result['trade_status'] == 'TRADE_SUCCESS') { | ||
| 154 | + $this->success("", "", ['status' => $result['trade_status']]); | ||
| 155 | + } else { | ||
| 156 | + $this->error("查询失败"); | ||
| 157 | + } | ||
| 158 | + } catch (GatewayException $e) { | ||
| 159 | + $this->error("查询失败"); | ||
| 160 | + } | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + //发起PC支付(Scan支付)(PC扫码模式) | ||
| 164 | + $orderData = Session::get("alipayorderdata"); | ||
| 165 | + if (!$orderData) { | ||
| 166 | + $this->error("请求参数错误"); | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + $orderData['method'] = 'scan'; | ||
| 170 | + $payData = Service::submitOrder($orderData); | ||
| 171 | + if (!isset($payData['qr_code'])) { | ||
| 172 | + $this->error("创建订单失败,请返回重试"); | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + $type = 'pc'; | ||
| 176 | + $this->view->assign("orderData", $orderData); | ||
| 177 | + $this->view->assign("payData", $payData); | ||
| 178 | + $this->view->assign("type", $type); | ||
| 179 | + $this->view->assign("title", "支付宝支付"); | ||
| 180 | + return $this->view->fetch(); | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + /** | ||
| 184 | + * 支付成功回调 | ||
| 185 | + */ | ||
| 186 | + public function notifyx() | ||
| 187 | + { | ||
| 188 | + $type = $this->request->param('type'); | ||
| 189 | + $pay = \addons\epay\library\Service::checkNotify($type); | ||
| 190 | + if (!$pay) { | ||
| 191 | + echo '签名错误'; | ||
| 192 | + return; | ||
| 193 | + } | ||
| 194 | + $data = $pay->verify(); | ||
| 195 | + | ||
| 196 | + //你可以在这里你的业务处理逻辑,比如处理你的订单状态、给会员加余额等等功能 | ||
| 197 | + //下面这句必须要执行,且在此之前不能有任何输出 | ||
| 198 | + return $pay->success()->send(); | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + /** | ||
| 202 | + * 支付成功返回 | ||
| 203 | + */ | ||
| 204 | + public function returnx() | ||
| 205 | + { | ||
| 206 | + $type = $this->request->param('type'); | ||
| 207 | + if (Service::checkReturn($type)) { | ||
| 208 | + echo '签名错误'; | ||
| 209 | + return; | ||
| 210 | + } | ||
| 211 | + | ||
| 212 | + //你可以在这里定义你的提示信息,但切记不可在此编写逻辑 | ||
| 213 | + $this->success("恭喜你!支付成功!", addon_url("epay/index/index")); | ||
| 214 | + | ||
| 215 | + return; | ||
| 216 | + } | ||
| 217 | + | ||
| 218 | +} |
addons/epay/controller/Index.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace addons\epay\controller; | ||
| 4 | + | ||
| 5 | +use addons\epay\library\Service; | ||
| 6 | +use fast\Random; | ||
| 7 | +use think\addons\Controller; | ||
| 8 | +use Exception; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 微信支付宝整合插件首页 | ||
| 12 | + * | ||
| 13 | + * 此控制器仅用于开发展示说明和测试,请自行添加一个新的控制器进行处理返回和回调事件,同时删除此控制器文件 | ||
| 14 | + * | ||
| 15 | + * Class Index | ||
| 16 | + * @package addons\epay\controller | ||
| 17 | + */ | ||
| 18 | +class Index extends Controller | ||
| 19 | +{ | ||
| 20 | + | ||
| 21 | + protected $layout = 'default'; | ||
| 22 | + | ||
| 23 | + protected $config = []; | ||
| 24 | + | ||
| 25 | + public function _initialize() | ||
| 26 | + { | ||
| 27 | + parent::_initialize(); | ||
| 28 | + if (!config("app_debug")) { | ||
| 29 | + $this->error("仅在开发环境下查看"); | ||
| 30 | + } | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + public function index() | ||
| 34 | + { | ||
| 35 | + $this->view->assign("title", "微信支付宝整合"); | ||
| 36 | + return $this->view->fetch(); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + /** | ||
| 40 | + * 体验,仅供开发测试 | ||
| 41 | + */ | ||
| 42 | + public function experience() | ||
| 43 | + { | ||
| 44 | + $amount = $this->request->request('amount'); | ||
| 45 | + $type = $this->request->request('type'); | ||
| 46 | + $method = $this->request->request('method'); | ||
| 47 | + | ||
| 48 | + if (!$amount || $amount < 0) { | ||
| 49 | + $this->error("支付金额必须大于0"); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + if (!$type || !in_array($type, ['alipay', 'wechat'])) { | ||
| 53 | + $this->error("支付类型不能为空"); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + //订单号 | ||
| 57 | + $out_trade_no = date("YmdHis") . mt_rand(100000, 999999); | ||
| 58 | + | ||
| 59 | + //订单标题 | ||
| 60 | + $title = '测试订单'; | ||
| 61 | + | ||
| 62 | + //回调链接 | ||
| 63 | + $notifyurl = $this->request->root(true) . '/addons/epay/index/notifyx/paytype/' . $type; | ||
| 64 | + $returnurl = $this->request->root(true) . '/addons/epay/index/returnx/paytype/' . $type . '/out_trade_no/' . $out_trade_no; | ||
| 65 | + | ||
| 66 | + $response = Service::submitOrder($amount, $out_trade_no, $type, $title, $notifyurl, $returnurl, $method); | ||
| 67 | + | ||
| 68 | + return $response; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + /** | ||
| 72 | + * 支付成功,仅供开发测试 | ||
| 73 | + */ | ||
| 74 | + public function notifyx() | ||
| 75 | + { | ||
| 76 | + $paytype = $this->request->param('paytype'); | ||
| 77 | + $pay = Service::checkNotify($paytype); | ||
| 78 | + if (!$pay) { | ||
| 79 | + echo '签名错误'; | ||
| 80 | + return; | ||
| 81 | + } | ||
| 82 | + $data = $pay->verify(); | ||
| 83 | + try { | ||
| 84 | + $payamount = $paytype == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100; | ||
| 85 | + $out_trade_no = $data['out_trade_no']; | ||
| 86 | + | ||
| 87 | + //你可以在此编写订单逻辑 | ||
| 88 | + } catch (Exception $e) { | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + //下面这句必须要执行,且在此之前不能有任何输出 | ||
| 92 | + return $pay->success()->send(); | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + /** | ||
| 96 | + * 支付返回,仅供开发测试 | ||
| 97 | + */ | ||
| 98 | + public function returnx() | ||
| 99 | + { | ||
| 100 | + $paytype = $this->request->param('paytype'); | ||
| 101 | + $out_trade_no = $this->request->param('out_trade_no'); | ||
| 102 | + $pay = Service::checkReturn($paytype); | ||
| 103 | + if (!$pay) { | ||
| 104 | + $this->error('签名错误', ''); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + //你可以在这里通过out_trade_no去验证订单状态 | ||
| 108 | + //但是不可以在此编写订单逻辑!!! | ||
| 109 | + | ||
| 110 | + $this->success("请返回网站查看支付结果", addon_url("epay/index/index")); | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | +} |
addons/epay/info.ini
0 → 100644
addons/epay/library/Collection.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace addons\epay\library; | ||
| 4 | + | ||
| 5 | +class Collection extends \Yansongda\Supports\Collection | ||
| 6 | +{ | ||
| 7 | + | ||
| 8 | + /** | ||
| 9 | + * 创建 Collection 实例 | ||
| 10 | + * @access public | ||
| 11 | + * @param array $items 数据 | ||
| 12 | + * @return static | ||
| 13 | + */ | ||
| 14 | + public static function make($items = []) | ||
| 15 | + { | ||
| 16 | + return new static($items); | ||
| 17 | + } | ||
| 18 | +} |
addons/epay/library/OrderException.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace addons\epay\library; | ||
| 4 | + | ||
| 5 | +use think\Exception; | ||
| 6 | + | ||
| 7 | +class OrderException extends Exception | ||
| 8 | +{ | ||
| 9 | + public function __construct($message = "", $code = 0, $data = []) | ||
| 10 | + { | ||
| 11 | + $this->message = $message; | ||
| 12 | + $this->code = $code; | ||
| 13 | + $this->data = $data; | ||
| 14 | + } | ||
| 15 | + | ||
| 16 | +} |
addons/epay/library/RedirectResponse.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace addons\epay\library; | ||
| 4 | + | ||
| 5 | +class RedirectResponse extends \Symfony\Component\HttpFoundation\RedirectResponse implements \JsonSerializable, \Serializable | ||
| 6 | +{ | ||
| 7 | + public function __toString() | ||
| 8 | + { | ||
| 9 | + return $this->getContent(); | ||
| 10 | + } | ||
| 11 | + | ||
| 12 | + public function setTargetUrl($url) | ||
| 13 | + { | ||
| 14 | + if ('' === ($url ?? '')) { | ||
| 15 | + throw new \InvalidArgumentException('无法跳转到空页面'); | ||
| 16 | + } | ||
| 17 | + | ||
| 18 | + $this->targetUrl = $url; | ||
| 19 | + | ||
| 20 | + $this->setContent( | ||
| 21 | + sprintf('<!DOCTYPE html> | ||
| 22 | +<html> | ||
| 23 | + <head> | ||
| 24 | + <meta charset="UTF-8" /> | ||
| 25 | + <meta http-equiv="refresh" content="0;url=\'%1$s\'" /> | ||
| 26 | + | ||
| 27 | + <title>正在跳转支付 %1$s</title> | ||
| 28 | + </head> | ||
| 29 | + <body> | ||
| 30 | + <div id="redirect" style="display:none;">正在跳转支付 <a href="%1$s">%1$s</a></div> | ||
| 31 | + <script type="text/javascript"> | ||
| 32 | + setTimeout(function(){ | ||
| 33 | + document.getElementById("redirect").style.display = "block"; | ||
| 34 | + }, 1000); | ||
| 35 | + </script> | ||
| 36 | + </body> | ||
| 37 | +</html>', htmlspecialchars($url, \ENT_QUOTES, 'UTF-8'))); | ||
| 38 | + | ||
| 39 | + $this->headers->set('Location', $url); | ||
| 40 | + | ||
| 41 | + return $this; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + public function jsonSerialize() | ||
| 45 | + { | ||
| 46 | + return $this->getContent(); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + public function serialize() | ||
| 50 | + { | ||
| 51 | + return serialize($this->content); | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + public function unserialize($serialized) | ||
| 55 | + { | ||
| 56 | + return $this->content = unserialize($serialized); | ||
| 57 | + } | ||
| 58 | +} |
addons/epay/library/Response.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace addons\epay\library; | ||
| 4 | + | ||
| 5 | +class Response extends \Symfony\Component\HttpFoundation\Response implements \JsonSerializable, \Serializable | ||
| 6 | +{ | ||
| 7 | + public function __toString() | ||
| 8 | + { | ||
| 9 | + return $this->getContent(); | ||
| 10 | + } | ||
| 11 | + | ||
| 12 | + public function jsonSerialize() | ||
| 13 | + { | ||
| 14 | + return $this->getContent(); | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + public function serialize() | ||
| 18 | + { | ||
| 19 | + return serialize($this->content); | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + public function unserialize($serialized) | ||
| 23 | + { | ||
| 24 | + return $this->content = unserialize($serialized); | ||
| 25 | + } | ||
| 26 | +} |
addons/epay/library/Service.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace addons\epay\library; | ||
| 4 | + | ||
| 5 | +use addons\third\model\Third; | ||
| 6 | +use app\common\library\Auth; | ||
| 7 | +use Exception; | ||
| 8 | +use think\Session; | ||
| 9 | +use Yansongda\Pay\Pay; | ||
| 10 | +use Yansongda\Supports\Str; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 订单服务类 | ||
| 14 | + * | ||
| 15 | + * @package addons\epay\library | ||
| 16 | + */ | ||
| 17 | +class Service | ||
| 18 | +{ | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 提交订单 | ||
| 22 | + * @param array|float $amount 订单金额 | ||
| 23 | + * @param string $orderid 订单号 | ||
| 24 | + * @param string $type 支付类型,可选alipay或wechat | ||
| 25 | + * @param string $title 订单标题 | ||
| 26 | + * @param string $notifyurl 通知回调URL | ||
| 27 | + * @param string $returnurl 跳转返回URL | ||
| 28 | + * @param string $method 支付方法 | ||
| 29 | + * @return Response|RedirectResponse|Collection | ||
| 30 | + * @throws Exception | ||
| 31 | + */ | ||
| 32 | + public static function submitOrder($amount, $orderid = null, $type = null, $title = null, $notifyurl = null, $returnurl = null, $method = null, $openid = '') | ||
| 33 | + { | ||
| 34 | + if (!is_array($amount)) { | ||
| 35 | + $params = [ | ||
| 36 | + 'amount' => $amount, | ||
| 37 | + 'orderid' => $orderid, | ||
| 38 | + 'type' => $type, | ||
| 39 | + 'title' => $title, | ||
| 40 | + 'notifyurl' => $notifyurl, | ||
| 41 | + 'returnurl' => $returnurl, | ||
| 42 | + 'method' => $method, | ||
| 43 | + 'openid' => $openid, | ||
| 44 | + ]; | ||
| 45 | + } else { | ||
| 46 | + $params = $amount; | ||
| 47 | + } | ||
| 48 | + $type = isset($params['type']) && in_array($params['type'], ['alipay', 'wechat']) ? $params['type'] : 'wechat'; | ||
| 49 | + $method = isset($params['method']) ? $params['method'] : 'web'; | ||
| 50 | + $orderid = isset($params['orderid']) ? $params['orderid'] : date("YmdHis") . mt_rand(100000, 999999); | ||
| 51 | + $amount = isset($params['amount']) ? $params['amount'] : 1; | ||
| 52 | + $title = isset($params['title']) ? $params['title'] : "支付"; | ||
| 53 | + $auth_code = isset($params['auth_code']) ? $params['auth_code'] : ''; | ||
| 54 | + $openid = isset($params['openid']) ? $params['openid'] : ''; | ||
| 55 | + | ||
| 56 | + $request = request(); | ||
| 57 | + $notifyurl = isset($params['notifyurl']) ? $params['notifyurl'] : $request->root(true) . '/addons/epay/index/' . $type . 'notify'; | ||
| 58 | + $returnurl = isset($params['returnurl']) ? $params['returnurl'] : $request->root(true) . '/addons/epay/index/' . $type . 'return/out_trade_no/' . $orderid; | ||
| 59 | + $html = ''; | ||
| 60 | + $config = Service::getConfig($type); | ||
| 61 | + $config['notify_url'] = $notifyurl; | ||
| 62 | + $config['return_url'] = $returnurl; | ||
| 63 | + $isWechat = strpos($request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false; | ||
| 64 | + | ||
| 65 | + $result = null; | ||
| 66 | + if ($type == 'alipay') { | ||
| 67 | + //如果是PC支付,判断当前环境,进行跳转 | ||
| 68 | + if ($method == 'web') { | ||
| 69 | + //如果是微信环境或后台配置PC使用扫码支付 | ||
| 70 | + if ($isWechat || $config['scanpay']) { | ||
| 71 | + Session::set("alipayorderdata", $params); | ||
| 72 | + $url = addon_url('epay/api/alipay', [], true, true); | ||
| 73 | + return RedirectResponse::create($url); | ||
| 74 | + } elseif ($request->isMobile()) { | ||
| 75 | + $method = 'wap'; | ||
| 76 | + } | ||
| 77 | + } | ||
| 78 | + //创建支付对象 | ||
| 79 | + $pay = Pay::alipay($config); | ||
| 80 | + $params = [ | ||
| 81 | + 'out_trade_no' => $orderid,//你的订单号 | ||
| 82 | + 'total_amount' => $amount,//单位元 | ||
| 83 | + 'subject' => $title, | ||
| 84 | + ]; | ||
| 85 | + | ||
| 86 | + switch ($method) { | ||
| 87 | + case 'web': | ||
| 88 | + //电脑支付 | ||
| 89 | + $result = $pay->web($params); | ||
| 90 | + break; | ||
| 91 | + case 'wap': | ||
| 92 | + //手机网页支付 | ||
| 93 | + $result = $pay->wap($params); | ||
| 94 | + break; | ||
| 95 | + case 'app': | ||
| 96 | + //APP支付 | ||
| 97 | + $result = $pay->app($params); | ||
| 98 | + break; | ||
| 99 | + case 'scan': | ||
| 100 | + //扫码支付 | ||
| 101 | + $result = $pay->scan($params); | ||
| 102 | + break; | ||
| 103 | + case 'pos': | ||
| 104 | + //刷卡支付必须要有auth_code | ||
| 105 | + $params['auth_code'] = $auth_code; | ||
| 106 | + $result = $pay->pos($params); | ||
| 107 | + break; | ||
| 108 | + case 'mini': | ||
| 109 | + case 'miniapp': | ||
| 110 | + //小程序支付 | ||
| 111 | + //小程序支付,直接返回字符串 | ||
| 112 | + //小程序支付必须要有buyer_id,这里使用openid | ||
| 113 | + $params['buyer_id'] = $openid; | ||
| 114 | + $result = $pay->mini($params); | ||
| 115 | + break; | ||
| 116 | + default: | ||
| 117 | + } | ||
| 118 | + } else { | ||
| 119 | + //如果是PC支付,判断当前环境,进行跳转 | ||
| 120 | + if ($method == 'web') { | ||
| 121 | + //如果是移动端,但不是微信环境 | ||
| 122 | + if ($request->isMobile() && !$isWechat) { | ||
| 123 | + $method = 'wap'; | ||
| 124 | + } else { | ||
| 125 | + Session::set("wechatorderdata", $params); | ||
| 126 | + $url = addon_url('epay/api/wechat', [], true, true); | ||
| 127 | + return RedirectResponse::create($url); | ||
| 128 | + } | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + //创建支付对象 | ||
| 132 | + $pay = Pay::wechat($config); | ||
| 133 | + $params = [ | ||
| 134 | + 'out_trade_no' => $orderid,//你的订单号 | ||
| 135 | + 'body' => $title, | ||
| 136 | + 'total_fee' => $amount * 100, //单位分 | ||
| 137 | + ]; | ||
| 138 | + switch ($method) { | ||
| 139 | + //case 'web': | ||
| 140 | + // //电脑支付,跳转到自定义展示页面 | ||
| 141 | + // $result = $pay->web($params); | ||
| 142 | + // break; | ||
| 143 | + case 'mp': | ||
| 144 | + //公众号支付 | ||
| 145 | + //公众号支付必须有openid | ||
| 146 | + $params['openid'] = $openid; | ||
| 147 | + $result = $pay->mp($params); | ||
| 148 | + break; | ||
| 149 | + case 'wap': | ||
| 150 | + //手机网页支付,跳转 | ||
| 151 | + $params['spbill_create_ip'] = $request->ip(0, false); | ||
| 152 | + $result = $pay->wap($params); | ||
| 153 | + break; | ||
| 154 | + case 'app': | ||
| 155 | + //APP支付,直接返回字符串 | ||
| 156 | + $result = $pay->app($params); | ||
| 157 | + break; | ||
| 158 | + case 'scan': | ||
| 159 | + //扫码支付,直接返回字符串 | ||
| 160 | + $result = $pay->scan($params); | ||
| 161 | + break; | ||
| 162 | + case 'pos': | ||
| 163 | + //刷卡支付,直接返回字符串 | ||
| 164 | + //刷卡支付必须要有auth_code | ||
| 165 | + $params['auth_code'] = $auth_code; | ||
| 166 | + $result = $pay->pos($params); | ||
| 167 | + break; | ||
| 168 | + case 'mini': | ||
| 169 | + case 'miniapp': | ||
| 170 | + //小程序支付,直接返回字符串 | ||
| 171 | + //小程序支付必须要有openid | ||
| 172 | + $params['openid'] = $openid; | ||
| 173 | + $result = $pay->miniapp($params); | ||
| 174 | + break; | ||
| 175 | + default: | ||
| 176 | + } | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + //使用重写的Response类、RedirectResponse、Collection类 | ||
| 180 | + if ($result instanceof \Symfony\Component\HttpFoundation\RedirectResponse) { | ||
| 181 | + $result = RedirectResponse::create($result->getTargetUrl()); | ||
| 182 | + } elseif ($result instanceof \Symfony\Component\HttpFoundation\Response) { | ||
| 183 | + $result = Response::create($result->getContent()); | ||
| 184 | + } elseif ($result instanceof \Yansongda\Supports\Collection) { | ||
| 185 | + $result = Collection::make($result->all()); | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + return $result; | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + /** | ||
| 192 | + * 验证回调是否成功 | ||
| 193 | + * @param string $type 支付类型 | ||
| 194 | + * @param array $config 配置信息 | ||
| 195 | + * @return bool|\Yansongda\Pay\Gateways\Alipay|\Yansongda\Pay\Gateways\Wechat | ||
| 196 | + */ | ||
| 197 | + public static function checkNotify($type, $config = []) | ||
| 198 | + { | ||
| 199 | + $type = strtolower($type); | ||
| 200 | + if (!in_array($type, ['wechat', 'alipay'])) { | ||
| 201 | + return false; | ||
| 202 | + } | ||
| 203 | + try { | ||
| 204 | + $config = self::getConfig($type); | ||
| 205 | + $pay = $type == 'wechat' ? Pay::wechat($config) : Pay::alipay($config); | ||
| 206 | + $data = $pay->verify(); | ||
| 207 | + | ||
| 208 | + if ($type == 'alipay') { | ||
| 209 | + if (in_array($data['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED'])) { | ||
| 210 | + return $pay; | ||
| 211 | + } | ||
| 212 | + } else { | ||
| 213 | + return $pay; | ||
| 214 | + } | ||
| 215 | + } catch (Exception $e) { | ||
| 216 | + return false; | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + return false; | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + /** | ||
| 223 | + * 验证返回是否成功,请勿用于判断是否支付成功的逻辑验证 | ||
| 224 | + * 已弃用 | ||
| 225 | + * | ||
| 226 | + * @param string $type 支付类型 | ||
| 227 | + * @param array $config 配置信息 | ||
| 228 | + * @return bool | ||
| 229 | + * @deprecated 已弃用,请勿用于逻辑验证 | ||
| 230 | + */ | ||
| 231 | + public static function checkReturn($type, $config = []) | ||
| 232 | + { | ||
| 233 | + //由于PC及移动端无法获取请求的参数信息,取消return验证,均返回true | ||
| 234 | + return true; | ||
| 235 | + } | ||
| 236 | + | ||
| 237 | + /** | ||
| 238 | + * 获取配置 | ||
| 239 | + * @param string $type 支付类型 | ||
| 240 | + * @return array|mixed | ||
| 241 | + */ | ||
| 242 | + public static function getConfig($type = 'wechat') | ||
| 243 | + { | ||
| 244 | + $config = get_addon_config('epay'); | ||
| 245 | + $config = isset($config[$type]) ? $config[$type] : $config['wechat']; | ||
| 246 | + if ($config['log']) { | ||
| 247 | + $config['log'] = [ | ||
| 248 | + 'file' => LOG_PATH . 'epaylogs' . DS . $type . '-' . date("Y-m-d") . '.log', | ||
| 249 | + 'level' => 'debug' | ||
| 250 | + ]; | ||
| 251 | + } | ||
| 252 | + if (isset($config['cert_client']) && substr($config['cert_client'], 0, 8) == '/addons/') { | ||
| 253 | + $config['cert_client'] = ROOT_PATH . str_replace('/', DS, substr($config['cert_client'], 1)); | ||
| 254 | + } | ||
| 255 | + if (isset($config['cert_key']) && substr($config['cert_key'], 0, 8) == '/addons/') { | ||
| 256 | + $config['cert_key'] = ROOT_PATH . str_replace('/', DS, substr($config['cert_key'], 1)); | ||
| 257 | + } | ||
| 258 | + if (isset($config['app_cert_public_key']) && substr($config['app_cert_public_key'], 0, 8) == '/addons/') { | ||
| 259 | + $config['app_cert_public_key'] = ROOT_PATH . str_replace('/', DS, substr($config['app_cert_public_key'], 1)); | ||
| 260 | + } | ||
| 261 | + if (isset($config['alipay_root_cert']) && substr($config['alipay_root_cert'], 0, 8) == '/addons/') { | ||
| 262 | + $config['alipay_root_cert'] = ROOT_PATH . str_replace('/', DS, substr($config['alipay_root_cert'], 1)); | ||
| 263 | + } | ||
| 264 | + if (isset($config['ali_public_key']) && (Str::endsWith($config['ali_public_key'], '.crt') || Str::endsWith($config['ali_public_key'], '.pem'))) { | ||
| 265 | + $config['ali_public_key'] = ROOT_PATH . str_replace('/', DS, substr($config['ali_public_key'], 1)); | ||
| 266 | + } | ||
| 267 | + // 可选 | ||
| 268 | + $config['http'] = [ | ||
| 269 | + 'timeout' => 10, | ||
| 270 | + 'connect_timeout' => 10, | ||
| 271 | + // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html) | ||
| 272 | + ]; | ||
| 273 | + | ||
| 274 | + $config['notify_url'] = empty($config['notify_url']) ? addon_url('epay/api/notifyx', [], false) . '/type/' . $type : $config['notify_url']; | ||
| 275 | + $config['notify_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['notify_url']) ? request()->root(true) . $config['notify_url'] : $config['notify_url']; | ||
| 276 | + $config['return_url'] = empty($config['return_url']) ? addon_url('epay/api/returnx', [], false) . '/type/' . $type : $config['return_url']; | ||
| 277 | + $config['return_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['return_url']) ? request()->root(true) . $config['return_url'] : $config['return_url']; | ||
| 278 | + return $config; | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + /** | ||
| 282 | + * 获取微信Openid | ||
| 283 | + * | ||
| 284 | + * @return mixed|string | ||
| 285 | + */ | ||
| 286 | + public static function getOpenid() | ||
| 287 | + { | ||
| 288 | + $config = self::getConfig('wechat'); | ||
| 289 | + $openid = ''; | ||
| 290 | + $auth = Auth::instance(); | ||
| 291 | + if ($auth->isLogin()) { | ||
| 292 | + $third = get_addon_info('third'); | ||
| 293 | + if ($third && $third['state']) { | ||
| 294 | + $thirdInfo = Third::where('user_id', $auth->id)->where('platform', 'wechat')->where('apptype', 'mp')->find(); | ||
| 295 | + $openid = $thirdInfo ? $thirdInfo['openid'] : ''; | ||
| 296 | + } | ||
| 297 | + } | ||
| 298 | + if (!$openid) { | ||
| 299 | + $openid = Session::get("openid"); | ||
| 300 | + | ||
| 301 | + //如果未传openid,则去读取openid | ||
| 302 | + if (!$openid) { | ||
| 303 | + $wechat = new Wechat($config['app_id'], $config['app_secret']); | ||
| 304 | + $openid = $wechat->getOpenid(); | ||
| 305 | + } | ||
| 306 | + } | ||
| 307 | + return $openid; | ||
| 308 | + } | ||
| 309 | + | ||
| 310 | +} |
addons/epay/library/Wechat.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace addons\epay\library; | ||
| 4 | + | ||
| 5 | +use fast\Http; | ||
| 6 | +use think\Cache; | ||
| 7 | +use think\Session; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 微信授权 | ||
| 11 | + * | ||
| 12 | + */ | ||
| 13 | +class Wechat | ||
| 14 | +{ | ||
| 15 | + private $app_id = ''; | ||
| 16 | + private $app_secret = ''; | ||
| 17 | + private $scope = 'snsapi_userinfo'; | ||
| 18 | + | ||
| 19 | + public function __construct($app_id, $app_secret) | ||
| 20 | + { | ||
| 21 | + $this->app_id = $app_id; | ||
| 22 | + $this->app_secret = $app_secret; | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 获取微信授权链接 | ||
| 27 | + * | ||
| 28 | + * @return string | ||
| 29 | + */ | ||
| 30 | + public function getAuthorizeUrl() | ||
| 31 | + { | ||
| 32 | + $redirect_uri = addon_url('epay/api/wechat', [], true, true); | ||
| 33 | + $redirect_uri = urlencode($redirect_uri); | ||
| 34 | + $state = \fast\Random::alnum(); | ||
| 35 | + Session::set('state', $state); | ||
| 36 | + return "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$this->app_id}&redirect_uri={$redirect_uri}&response_type=code&scope={$this->scope}&state={$state}#wechat_redirect"; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + /** | ||
| 40 | + * 获取微信openid | ||
| 41 | + * | ||
| 42 | + * @return mixed|string | ||
| 43 | + */ | ||
| 44 | + public function getOpenid() | ||
| 45 | + { | ||
| 46 | + $openid = Session::get('openid'); | ||
| 47 | + if (!$openid) { | ||
| 48 | + if (!isset($_GET['code'])) { | ||
| 49 | + $url = $this->getAuthorizeUrl(); | ||
| 50 | + | ||
| 51 | + Header("Location: $url"); | ||
| 52 | + exit(); | ||
| 53 | + } else { | ||
| 54 | + $state = Session::get('state'); | ||
| 55 | + if ($state == $_GET['state']) { | ||
| 56 | + $code = $_GET['code']; | ||
| 57 | + $token = $this->getAccessToken($code); | ||
| 58 | + if (!isset($token['openid']) && isset($token['errmsg'])) { | ||
| 59 | + exception($token['errmsg']); | ||
| 60 | + } | ||
| 61 | + $openid = isset($token['openid']) ? $token['openid'] : ''; | ||
| 62 | + if ($openid) { | ||
| 63 | + Session::set("openid", $openid); | ||
| 64 | + } | ||
| 65 | + } | ||
| 66 | + } | ||
| 67 | + } | ||
| 68 | + return $openid; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + /** | ||
| 72 | + * 获取授权token网页授权 | ||
| 73 | + * | ||
| 74 | + * @param string $code | ||
| 75 | + * @return mixed|string | ||
| 76 | + */ | ||
| 77 | + public function getAccessToken($code = '') | ||
| 78 | + { | ||
| 79 | + $params = [ | ||
| 80 | + 'appid' => $this->app_id, | ||
| 81 | + 'secret' => $this->app_secret, | ||
| 82 | + 'code' => $code, | ||
| 83 | + 'grant_type' => 'authorization_code' | ||
| 84 | + ]; | ||
| 85 | + $ret = Http::sendRequest('https://api.weixin.qq.com/sns/oauth2/access_token', $params, 'GET'); | ||
| 86 | + if ($ret['ret']) { | ||
| 87 | + $ar = json_decode($ret['msg'], true); | ||
| 88 | + return $ar; | ||
| 89 | + } | ||
| 90 | + return []; | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + public function getJsticket($code = '') | ||
| 94 | + { | ||
| 95 | + $jsticket = Session::get('jsticket'); | ||
| 96 | + if (!$jsticket) { | ||
| 97 | + $token = $this->getAccessToken($code); | ||
| 98 | + $params = [ | ||
| 99 | + 'access_token' => 'token', | ||
| 100 | + 'type' => 'jsapi', | ||
| 101 | + ]; | ||
| 102 | + $ret = Http::sendRequest('https://api.weixin.qq.com/cgi-bin/ticket/getticket', $params, 'GET'); | ||
| 103 | + if ($ret['ret']) { | ||
| 104 | + $ar = json_decode($ret['msg'], true); | ||
| 105 | + return $ar; | ||
| 106 | + } | ||
| 107 | + } | ||
| 108 | + return $jsticket; | ||
| 109 | + } | ||
| 110 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Contracts; | ||
| 4 | + | ||
| 5 | +use Symfony\Component\HttpFoundation\Response; | ||
| 6 | +use Yansongda\Supports\Collection; | ||
| 7 | + | ||
| 8 | +interface GatewayApplicationInterface | ||
| 9 | +{ | ||
| 10 | + /** | ||
| 11 | + * To pay. | ||
| 12 | + * | ||
| 13 | + * @author yansongda <me@yansonga.cn> | ||
| 14 | + * | ||
| 15 | + * @param string $gateway | ||
| 16 | + * @param array $params | ||
| 17 | + * | ||
| 18 | + * @return Collection|Response | ||
| 19 | + */ | ||
| 20 | + public function pay($gateway, $params); | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * Query an order. | ||
| 24 | + * | ||
| 25 | + * @author yansongda <me@yansongda.cn> | ||
| 26 | + * | ||
| 27 | + * @param string|array $order | ||
| 28 | + * | ||
| 29 | + * @return Collection | ||
| 30 | + */ | ||
| 31 | + public function find($order, string $type); | ||
| 32 | + | ||
| 33 | + /** | ||
| 34 | + * Refund an order. | ||
| 35 | + * | ||
| 36 | + * @author yansongda <me@yansongda.cn> | ||
| 37 | + * | ||
| 38 | + * @return Collection | ||
| 39 | + */ | ||
| 40 | + public function refund(array $order); | ||
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * Cancel an order. | ||
| 44 | + * | ||
| 45 | + * @author yansongda <me@yansongda.cn> | ||
| 46 | + * | ||
| 47 | + * @param string|array $order | ||
| 48 | + * | ||
| 49 | + * @return Collection | ||
| 50 | + */ | ||
| 51 | + public function cancel($order); | ||
| 52 | + | ||
| 53 | + /** | ||
| 54 | + * Close an order. | ||
| 55 | + * | ||
| 56 | + * @author yansongda <me@yansongda.cn> | ||
| 57 | + * | ||
| 58 | + * @param string|array $order | ||
| 59 | + * | ||
| 60 | + * @return Collection | ||
| 61 | + */ | ||
| 62 | + public function close($order); | ||
| 63 | + | ||
| 64 | + /** | ||
| 65 | + * Verify a request. | ||
| 66 | + * | ||
| 67 | + * @author yansongda <me@yansongda.cn> | ||
| 68 | + * | ||
| 69 | + * @param string|array|null $content | ||
| 70 | + * | ||
| 71 | + * @return Collection | ||
| 72 | + */ | ||
| 73 | + public function verify($content, bool $refund); | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * Echo success to server. | ||
| 77 | + * | ||
| 78 | + * @author yansongda <me@yansongda.cn> | ||
| 79 | + * | ||
| 80 | + * @return Response | ||
| 81 | + */ | ||
| 82 | + public function success(); | ||
| 83 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Contracts; | ||
| 4 | + | ||
| 5 | +use Symfony\Component\HttpFoundation\Response; | ||
| 6 | +use Yansongda\Supports\Collection; | ||
| 7 | + | ||
| 8 | +interface GatewayInterface | ||
| 9 | +{ | ||
| 10 | + /** | ||
| 11 | + * Pay an order. | ||
| 12 | + * | ||
| 13 | + * @author yansongda <me@yansongda.cn> | ||
| 14 | + * | ||
| 15 | + * @param string $endpoint | ||
| 16 | + * | ||
| 17 | + * @return Collection|Response | ||
| 18 | + */ | ||
| 19 | + public function pay($endpoint, array $payload); | ||
| 20 | +} |
addons/epay/library/Yansongda/Pay/Events.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay; | ||
| 4 | + | ||
| 5 | +use Exception; | ||
| 6 | +use Symfony\Component\EventDispatcher\EventDispatcher; | ||
| 7 | +use Symfony\Component\EventDispatcher\EventSubscriberInterface; | ||
| 8 | +use Symfony\Contracts\EventDispatcher\Event; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * @author yansongda <me@yansongda.cn> | ||
| 12 | + * | ||
| 13 | + * @method static Event dispatch(Event $event) Dispatches an event to all registered listeners | ||
| 14 | + * @method static array getListeners($eventName = null) Gets the listeners of a specific event or all listeners sorted by descending priority. | ||
| 15 | + * @method static int|void getListenerPriority($eventName, $listener) Gets the listener priority for a specific event. | ||
| 16 | + * @method static bool hasListeners($eventName = null) Checks whether an event has any registered listeners. | ||
| 17 | + * @method static void addListener($eventName, $listener, $priority = 0) Adds an event listener that listens on the specified events. | ||
| 18 | + * @method static removeListener($eventName, $listener) Removes an event listener from the specified events. | ||
| 19 | + * @method static void addSubscriber(EventSubscriberInterface $subscriber) Adds an event subscriber. | ||
| 20 | + * @method static void removeSubscriber(EventSubscriberInterface $subscriber) | ||
| 21 | + */ | ||
| 22 | +class Events | ||
| 23 | +{ | ||
| 24 | + /** | ||
| 25 | + * dispatcher. | ||
| 26 | + * | ||
| 27 | + * @var EventDispatcher | ||
| 28 | + */ | ||
| 29 | + protected static $dispatcher; | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * Forward call. | ||
| 33 | + * | ||
| 34 | + * @author yansongda <me@yansongda.cn> | ||
| 35 | + * | ||
| 36 | + * @param string $method | ||
| 37 | + * @param array $args | ||
| 38 | + * | ||
| 39 | + * @throws Exception | ||
| 40 | + * | ||
| 41 | + * @return mixed | ||
| 42 | + */ | ||
| 43 | + public static function __callStatic($method, $args) | ||
| 44 | + { | ||
| 45 | + return call_user_func_array([self::getDispatcher(), $method], $args); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * Forward call. | ||
| 50 | + * | ||
| 51 | + * @author yansongda <me@yansongda.cn> | ||
| 52 | + * | ||
| 53 | + * @param string $method | ||
| 54 | + * @param array $args | ||
| 55 | + * | ||
| 56 | + * @throws Exception | ||
| 57 | + * | ||
| 58 | + * @return mixed | ||
| 59 | + */ | ||
| 60 | + public function __call($method, $args) | ||
| 61 | + { | ||
| 62 | + return call_user_func_array([self::getDispatcher(), $method], $args); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + /** | ||
| 66 | + * setDispatcher. | ||
| 67 | + * | ||
| 68 | + * @author yansongda <me@yansongda.cn> | ||
| 69 | + */ | ||
| 70 | + public static function setDispatcher(EventDispatcher $dispatcher) | ||
| 71 | + { | ||
| 72 | + self::$dispatcher = $dispatcher; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * getDispatcher. | ||
| 77 | + * | ||
| 78 | + * @author yansongda <me@yansongda.cn> | ||
| 79 | + */ | ||
| 80 | + public static function getDispatcher(): EventDispatcher | ||
| 81 | + { | ||
| 82 | + if (self::$dispatcher) { | ||
| 83 | + return self::$dispatcher; | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + return self::$dispatcher = self::createDispatcher(); | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + /** | ||
| 90 | + * createDispatcher. | ||
| 91 | + * | ||
| 92 | + * @author yansongda <me@yansongda.cn> | ||
| 93 | + */ | ||
| 94 | + public static function createDispatcher(): EventDispatcher | ||
| 95 | + { | ||
| 96 | + return new EventDispatcher(); | ||
| 97 | + } | ||
| 98 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Events; | ||
| 4 | + | ||
| 5 | +class ApiRequested extends Event | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Endpoint. | ||
| 9 | + * | ||
| 10 | + * @var string | ||
| 11 | + */ | ||
| 12 | + public $endpoint; | ||
| 13 | + | ||
| 14 | + /** | ||
| 15 | + * Result. | ||
| 16 | + * | ||
| 17 | + * @var array | ||
| 18 | + */ | ||
| 19 | + public $result; | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * Bootstrap. | ||
| 23 | + */ | ||
| 24 | + public function __construct(string $driver, string $gateway, string $endpoint, array $result) | ||
| 25 | + { | ||
| 26 | + $this->endpoint = $endpoint; | ||
| 27 | + $this->result = $result; | ||
| 28 | + | ||
| 29 | + parent::__construct($driver, $gateway); | ||
| 30 | + } | ||
| 31 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Events; | ||
| 4 | + | ||
| 5 | +class ApiRequesting extends Event | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Endpoint. | ||
| 9 | + * | ||
| 10 | + * @var string | ||
| 11 | + */ | ||
| 12 | + public $endpoint; | ||
| 13 | + | ||
| 14 | + /** | ||
| 15 | + * Payload. | ||
| 16 | + * | ||
| 17 | + * @var array | ||
| 18 | + */ | ||
| 19 | + public $payload; | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * Bootstrap. | ||
| 23 | + */ | ||
| 24 | + public function __construct(string $driver, string $gateway, string $endpoint, array $payload) | ||
| 25 | + { | ||
| 26 | + $this->endpoint = $endpoint; | ||
| 27 | + $this->payload = $payload; | ||
| 28 | + | ||
| 29 | + parent::__construct($driver, $gateway); | ||
| 30 | + } | ||
| 31 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Events; | ||
| 4 | + | ||
| 5 | +use Symfony\Contracts\EventDispatcher\Event as SymfonyEvent; | ||
| 6 | + | ||
| 7 | +class Event extends SymfonyEvent | ||
| 8 | +{ | ||
| 9 | + /** | ||
| 10 | + * Driver. | ||
| 11 | + * | ||
| 12 | + * @var string | ||
| 13 | + */ | ||
| 14 | + public $driver; | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * Method. | ||
| 18 | + * | ||
| 19 | + * @var string | ||
| 20 | + */ | ||
| 21 | + public $gateway; | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * Extra attributes. | ||
| 25 | + * | ||
| 26 | + * @var mixed | ||
| 27 | + */ | ||
| 28 | + public $attributes; | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * Bootstrap. | ||
| 32 | + * | ||
| 33 | + * @author yansongda <me@yansongda.cn> | ||
| 34 | + */ | ||
| 35 | + public function __construct(string $driver, string $gateway) | ||
| 36 | + { | ||
| 37 | + $this->driver = $driver; | ||
| 38 | + $this->gateway = $gateway; | ||
| 39 | + } | ||
| 40 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Events; | ||
| 4 | + | ||
| 5 | +class MethodCalled extends Event | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * endpoint. | ||
| 9 | + * | ||
| 10 | + * @var string | ||
| 11 | + */ | ||
| 12 | + public $endpoint; | ||
| 13 | + | ||
| 14 | + /** | ||
| 15 | + * payload. | ||
| 16 | + * | ||
| 17 | + * @var array | ||
| 18 | + */ | ||
| 19 | + public $payload; | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * Bootstrap. | ||
| 23 | + * | ||
| 24 | + * @author yansongda <me@yansongda.cn> | ||
| 25 | + */ | ||
| 26 | + public function __construct(string $driver, string $gateway, string $endpoint, array $payload = []) | ||
| 27 | + { | ||
| 28 | + $this->endpoint = $endpoint; | ||
| 29 | + $this->payload = $payload; | ||
| 30 | + | ||
| 31 | + parent::__construct($driver, $gateway); | ||
| 32 | + } | ||
| 33 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Events; | ||
| 4 | + | ||
| 5 | +class PayStarted extends Event | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Endpoint. | ||
| 9 | + * | ||
| 10 | + * @var string | ||
| 11 | + */ | ||
| 12 | + public $endpoint; | ||
| 13 | + | ||
| 14 | + /** | ||
| 15 | + * Payload. | ||
| 16 | + * | ||
| 17 | + * @var array | ||
| 18 | + */ | ||
| 19 | + public $payload; | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * Bootstrap. | ||
| 23 | + */ | ||
| 24 | + public function __construct(string $driver, string $gateway, string $endpoint, array $payload) | ||
| 25 | + { | ||
| 26 | + $this->endpoint = $endpoint; | ||
| 27 | + $this->payload = $payload; | ||
| 28 | + | ||
| 29 | + parent::__construct($driver, $gateway); | ||
| 30 | + } | ||
| 31 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Events; | ||
| 4 | + | ||
| 5 | +class PayStarting extends Event | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Params. | ||
| 9 | + * | ||
| 10 | + * @var array | ||
| 11 | + */ | ||
| 12 | + public $params; | ||
| 13 | + | ||
| 14 | + /** | ||
| 15 | + * Bootstrap. | ||
| 16 | + */ | ||
| 17 | + public function __construct(string $driver, string $gateway, array $params) | ||
| 18 | + { | ||
| 19 | + $this->params = $params; | ||
| 20 | + | ||
| 21 | + parent::__construct($driver, $gateway); | ||
| 22 | + } | ||
| 23 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Events; | ||
| 4 | + | ||
| 5 | +class RequestReceived extends Event | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Received data. | ||
| 9 | + * | ||
| 10 | + * @var array | ||
| 11 | + */ | ||
| 12 | + public $data; | ||
| 13 | + | ||
| 14 | + /** | ||
| 15 | + * Bootstrap. | ||
| 16 | + * | ||
| 17 | + * @author yansongda <me@yansongda.cn> | ||
| 18 | + */ | ||
| 19 | + public function __construct(string $driver, string $gateway, array $data) | ||
| 20 | + { | ||
| 21 | + $this->data = $data; | ||
| 22 | + | ||
| 23 | + parent::__construct($driver, $gateway); | ||
| 24 | + } | ||
| 25 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Events; | ||
| 4 | + | ||
| 5 | +class SignFailed extends Event | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Received data. | ||
| 9 | + * | ||
| 10 | + * @var array | ||
| 11 | + */ | ||
| 12 | + public $data; | ||
| 13 | + | ||
| 14 | + /** | ||
| 15 | + * Bootstrap. | ||
| 16 | + * | ||
| 17 | + * @author yansongda <me@yansongda.cn> | ||
| 18 | + */ | ||
| 19 | + public function __construct(string $driver, string $gateway, array $data) | ||
| 20 | + { | ||
| 21 | + $this->data = $data; | ||
| 22 | + | ||
| 23 | + parent::__construct($driver, $gateway); | ||
| 24 | + } | ||
| 25 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Exceptions; | ||
| 4 | + | ||
| 5 | +class BusinessException extends GatewayException | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Bootstrap. | ||
| 9 | + * | ||
| 10 | + * @author yansongda <me@yansonga.cn> | ||
| 11 | + * | ||
| 12 | + * @param string $message | ||
| 13 | + * @param array|string $raw | ||
| 14 | + */ | ||
| 15 | + public function __construct($message, $raw = []) | ||
| 16 | + { | ||
| 17 | + parent::__construct('ERROR_BUSINESS: '.$message, $raw, self::ERROR_BUSINESS); | ||
| 18 | + } | ||
| 19 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Exceptions; | ||
| 4 | + | ||
| 5 | +class Exception extends \Exception | ||
| 6 | +{ | ||
| 7 | + const UNKNOWN_ERROR = 9999; | ||
| 8 | + | ||
| 9 | + const INVALID_GATEWAY = 1; | ||
| 10 | + | ||
| 11 | + const INVALID_CONFIG = 2; | ||
| 12 | + | ||
| 13 | + const INVALID_ARGUMENT = 3; | ||
| 14 | + | ||
| 15 | + const ERROR_GATEWAY = 4; | ||
| 16 | + | ||
| 17 | + const INVALID_SIGN = 5; | ||
| 18 | + | ||
| 19 | + const ERROR_BUSINESS = 6; | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * Raw error info. | ||
| 23 | + * | ||
| 24 | + * @var array | ||
| 25 | + */ | ||
| 26 | + public $raw; | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * Bootstrap. | ||
| 30 | + * | ||
| 31 | + * @author yansongda <me@yansonga.cn> | ||
| 32 | + * | ||
| 33 | + * @param string $message | ||
| 34 | + * @param array|string $raw | ||
| 35 | + * @param int|string $code | ||
| 36 | + */ | ||
| 37 | + public function __construct($message = '', $raw = [], $code = self::UNKNOWN_ERROR) | ||
| 38 | + { | ||
| 39 | + $message = '' === $message ? 'Unknown Error' : $message; | ||
| 40 | + $this->raw = is_array($raw) ? $raw : [$raw]; | ||
| 41 | + | ||
| 42 | + parent::__construct($message, intval($code)); | ||
| 43 | + } | ||
| 44 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Exceptions; | ||
| 4 | + | ||
| 5 | +class GatewayException extends Exception | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Bootstrap. | ||
| 9 | + * | ||
| 10 | + * @author yansongda <me@yansonga.cn> | ||
| 11 | + * | ||
| 12 | + * @param string $message | ||
| 13 | + * @param array|string $raw | ||
| 14 | + * @param int $code | ||
| 15 | + */ | ||
| 16 | + public function __construct($message, $raw = [], $code = self::ERROR_GATEWAY) | ||
| 17 | + { | ||
| 18 | + parent::__construct('ERROR_GATEWAY: '.$message, $raw, $code); | ||
| 19 | + } | ||
| 20 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Exceptions; | ||
| 4 | + | ||
| 5 | +class InvalidArgumentException extends Exception | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Bootstrap. | ||
| 9 | + * | ||
| 10 | + * @author yansongda <me@yansonga.cn> | ||
| 11 | + * | ||
| 12 | + * @param string $message | ||
| 13 | + * @param array|string $raw | ||
| 14 | + */ | ||
| 15 | + public function __construct($message, $raw = []) | ||
| 16 | + { | ||
| 17 | + parent::__construct('INVALID_ARGUMENT: '.$message, $raw, self::INVALID_ARGUMENT); | ||
| 18 | + } | ||
| 19 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Exceptions; | ||
| 4 | + | ||
| 5 | +class InvalidConfigException extends Exception | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Bootstrap. | ||
| 9 | + * | ||
| 10 | + * @author yansongda <me@yansonga.cn> | ||
| 11 | + * | ||
| 12 | + * @param string $message | ||
| 13 | + * @param array|string $raw | ||
| 14 | + */ | ||
| 15 | + public function __construct($message, $raw = []) | ||
| 16 | + { | ||
| 17 | + parent::__construct('INVALID_CONFIG: '.$message, $raw, self::INVALID_CONFIG); | ||
| 18 | + } | ||
| 19 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Exceptions; | ||
| 4 | + | ||
| 5 | +class InvalidGatewayException extends Exception | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Bootstrap. | ||
| 9 | + * | ||
| 10 | + * @author yansongda <me@yansonga.cn> | ||
| 11 | + * | ||
| 12 | + * @param string $message | ||
| 13 | + * @param array|string $raw | ||
| 14 | + */ | ||
| 15 | + public function __construct($message, $raw = []) | ||
| 16 | + { | ||
| 17 | + parent::__construct('INVALID_GATEWAY: '.$message, $raw, self::INVALID_GATEWAY); | ||
| 18 | + } | ||
| 19 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Exceptions; | ||
| 4 | + | ||
| 5 | +class InvalidSignException extends Exception | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Bootstrap. | ||
| 9 | + * | ||
| 10 | + * @author yansongda <me@yansonga.cn> | ||
| 11 | + * | ||
| 12 | + * @param string $message | ||
| 13 | + * @param array|string $raw | ||
| 14 | + */ | ||
| 15 | + public function __construct($message, $raw = []) | ||
| 16 | + { | ||
| 17 | + parent::__construct('INVALID_SIGN: '.$message, $raw, self::INVALID_SIGN); | ||
| 18 | + } | ||
| 19 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways; | ||
| 4 | + | ||
| 5 | +use Symfony\Component\HttpFoundation\Request; | ||
| 6 | +use Symfony\Component\HttpFoundation\Response; | ||
| 7 | +use Yansongda\Pay\Contracts\GatewayApplicationInterface; | ||
| 8 | +use Yansongda\Pay\Contracts\GatewayInterface; | ||
| 9 | +use Yansongda\Pay\Events; | ||
| 10 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 11 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 12 | +use Yansongda\Pay\Exceptions\InvalidConfigException; | ||
| 13 | +use Yansongda\Pay\Exceptions\InvalidGatewayException; | ||
| 14 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 15 | +use Yansongda\Pay\Gateways\Alipay\Support; | ||
| 16 | +use Yansongda\Supports\Collection; | ||
| 17 | +use Yansongda\Supports\Config; | ||
| 18 | +use Yansongda\Supports\Str; | ||
| 19 | + | ||
| 20 | +/** | ||
| 21 | + * @method Response app(array $config) APP 支付 | ||
| 22 | + * @method Collection pos(array $config) 刷卡支付 | ||
| 23 | + * @method Collection scan(array $config) 扫码支付 | ||
| 24 | + * @method Collection transfer(array $config) 帐户转账 | ||
| 25 | + * @method Response wap(array $config) 手机网站支付 | ||
| 26 | + * @method Response web(array $config) 电脑支付 | ||
| 27 | + * @method Collection mini(array $config) 小程序支付 | ||
| 28 | + */ | ||
| 29 | +class Alipay implements GatewayApplicationInterface | ||
| 30 | +{ | ||
| 31 | + /** | ||
| 32 | + * Const mode_normal. | ||
| 33 | + */ | ||
| 34 | + const MODE_NORMAL = 'normal'; | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * Const mode_dev. | ||
| 38 | + */ | ||
| 39 | + const MODE_DEV = 'dev'; | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * Const mode_service. | ||
| 43 | + */ | ||
| 44 | + const MODE_SERVICE = 'service'; | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * Const url. | ||
| 48 | + */ | ||
| 49 | + const URL = [ | ||
| 50 | + self::MODE_NORMAL => 'https://openapi.alipay.com/gateway.do?charset=utf-8', | ||
| 51 | + self::MODE_DEV => 'https://openapi.alipaydev.com/gateway.do?charset=utf-8', | ||
| 52 | + ]; | ||
| 53 | + | ||
| 54 | + /** | ||
| 55 | + * Alipay payload. | ||
| 56 | + * | ||
| 57 | + * @var array | ||
| 58 | + */ | ||
| 59 | + protected $payload; | ||
| 60 | + | ||
| 61 | + /** | ||
| 62 | + * Alipay gateway. | ||
| 63 | + * | ||
| 64 | + * @var string | ||
| 65 | + */ | ||
| 66 | + protected $gateway; | ||
| 67 | + | ||
| 68 | + /** | ||
| 69 | + * extends. | ||
| 70 | + * | ||
| 71 | + * @var array | ||
| 72 | + */ | ||
| 73 | + protected $extends; | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * Bootstrap. | ||
| 77 | + * | ||
| 78 | + * @author yansongda <me@yansongda.cn> | ||
| 79 | + * | ||
| 80 | + * @throws \Exception | ||
| 81 | + */ | ||
| 82 | + public function __construct(Config $config) | ||
| 83 | + { | ||
| 84 | + $this->gateway = Support::create($config)->getBaseUri(); | ||
| 85 | + $this->payload = [ | ||
| 86 | + 'app_id' => $config->get('app_id'), | ||
| 87 | + 'method' => '', | ||
| 88 | + 'format' => 'JSON', | ||
| 89 | + 'charset' => 'utf-8', | ||
| 90 | + 'sign_type' => 'RSA2', | ||
| 91 | + 'version' => '1.0', | ||
| 92 | + 'return_url' => $config->get('return_url'), | ||
| 93 | + 'notify_url' => $config->get('notify_url'), | ||
| 94 | + 'timestamp' => date('Y-m-d H:i:s'), | ||
| 95 | + 'sign' => '', | ||
| 96 | + 'biz_content' => '', | ||
| 97 | + 'app_auth_token' => $config->get('app_auth_token'), | ||
| 98 | + ]; | ||
| 99 | + | ||
| 100 | + if ($config->get('app_cert_public_key') && $config->get('alipay_root_cert')) { | ||
| 101 | + $this->payload['app_cert_sn'] = Support::getCertSN($config->get('app_cert_public_key')); | ||
| 102 | + $this->payload['alipay_root_cert_sn'] = Support::getRootCertSN($config->get('alipay_root_cert')); | ||
| 103 | + } | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + /** | ||
| 107 | + * Magic pay. | ||
| 108 | + * | ||
| 109 | + * @author yansongda <me@yansongda.cn> | ||
| 110 | + * | ||
| 111 | + * @param string $method | ||
| 112 | + * @param array $params | ||
| 113 | + * | ||
| 114 | + * @throws GatewayException | ||
| 115 | + * @throws InvalidArgumentException | ||
| 116 | + * @throws InvalidConfigException | ||
| 117 | + * @throws InvalidGatewayException | ||
| 118 | + * @throws InvalidSignException | ||
| 119 | + * | ||
| 120 | + * @return Response|Collection | ||
| 121 | + */ | ||
| 122 | + public function __call($method, $params) | ||
| 123 | + { | ||
| 124 | + if (isset($this->extends[$method])) { | ||
| 125 | + return $this->makeExtend($method, ...$params); | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + return $this->pay($method, ...$params); | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + /** | ||
| 132 | + * Pay an order. | ||
| 133 | + * | ||
| 134 | + * @author yansongda <me@yansongda.cn> | ||
| 135 | + * | ||
| 136 | + * @param string $gateway | ||
| 137 | + * @param array $params | ||
| 138 | + * | ||
| 139 | + * @throws InvalidGatewayException | ||
| 140 | + * | ||
| 141 | + * @return Response|Collection | ||
| 142 | + */ | ||
| 143 | + public function pay($gateway, $params = []) | ||
| 144 | + { | ||
| 145 | + Events::dispatch(new Events\PayStarting('Alipay', $gateway, $params)); | ||
| 146 | + | ||
| 147 | + $this->payload['return_url'] = $params['return_url'] ?? $this->payload['return_url']; | ||
| 148 | + $this->payload['notify_url'] = $params['notify_url'] ?? $this->payload['notify_url']; | ||
| 149 | + | ||
| 150 | + unset($params['return_url'], $params['notify_url']); | ||
| 151 | + | ||
| 152 | + $this->payload['biz_content'] = json_encode($params); | ||
| 153 | + | ||
| 154 | + $gateway = get_class($this).'\\'.Str::studly($gateway).'Gateway'; | ||
| 155 | + | ||
| 156 | + if (class_exists($gateway)) { | ||
| 157 | + return $this->makePay($gateway); | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + throw new InvalidGatewayException("Pay Gateway [{$gateway}] not exists"); | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + /** | ||
| 164 | + * Verify sign. | ||
| 165 | + * | ||
| 166 | + * @author yansongda <me@yansongda.cn> | ||
| 167 | + * | ||
| 168 | + * @param array|null $data | ||
| 169 | + * | ||
| 170 | + * @throws InvalidSignException | ||
| 171 | + * @throws InvalidConfigException | ||
| 172 | + */ | ||
| 173 | + public function verify($data = null, bool $refund = false): Collection | ||
| 174 | + { | ||
| 175 | + if (is_null($data)) { | ||
| 176 | + $request = Request::createFromGlobals(); | ||
| 177 | + | ||
| 178 | + $data = $request->request->count() > 0 ? $request->request->all() : $request->query->all(); | ||
| 179 | + } | ||
| 180 | + | ||
| 181 | + if (isset($data['fund_bill_list'])) { | ||
| 182 | + $data['fund_bill_list'] = htmlspecialchars_decode($data['fund_bill_list']); | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + Events::dispatch(new Events\RequestReceived('Alipay', '', $data)); | ||
| 186 | + | ||
| 187 | + if (Support::verifySign($data)) { | ||
| 188 | + return new Collection($data); | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + Events::dispatch(new Events\SignFailed('Alipay', '', $data)); | ||
| 192 | + | ||
| 193 | + throw new InvalidSignException('Alipay Sign Verify FAILED', $data); | ||
| 194 | + } | ||
| 195 | + | ||
| 196 | + /** | ||
| 197 | + * Query an order. | ||
| 198 | + * | ||
| 199 | + * @author yansongda <me@yansongda.cn> | ||
| 200 | + * | ||
| 201 | + * @param string|array $order | ||
| 202 | + * | ||
| 203 | + * @throws GatewayException | ||
| 204 | + * @throws InvalidConfigException | ||
| 205 | + * @throws InvalidSignException | ||
| 206 | + */ | ||
| 207 | + public function find($order, string $type = 'wap'): Collection | ||
| 208 | + { | ||
| 209 | + $gateway = get_class($this).'\\'.Str::studly($type).'Gateway'; | ||
| 210 | + | ||
| 211 | + if (!class_exists($gateway) || !is_callable([new $gateway(), 'find'])) { | ||
| 212 | + throw new GatewayException("{$gateway} Done Not Exist Or Done Not Has FIND Method"); | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + $config = call_user_func([new $gateway(), 'find'], $order); | ||
| 216 | + | ||
| 217 | + $this->payload['method'] = $config['method']; | ||
| 218 | + $this->payload['biz_content'] = $config['biz_content']; | ||
| 219 | + $this->payload['sign'] = Support::generateSign($this->payload); | ||
| 220 | + | ||
| 221 | + Events::dispatch(new Events\MethodCalled('Alipay', 'Find', $this->gateway, $this->payload)); | ||
| 222 | + | ||
| 223 | + return Support::requestApi($this->payload); | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | + /** | ||
| 227 | + * Refund an order. | ||
| 228 | + * | ||
| 229 | + * @author yansongda <me@yansongda.cn> | ||
| 230 | + * | ||
| 231 | + * @throws GatewayException | ||
| 232 | + * @throws InvalidConfigException | ||
| 233 | + * @throws InvalidSignException | ||
| 234 | + */ | ||
| 235 | + public function refund(array $order): Collection | ||
| 236 | + { | ||
| 237 | + $this->payload['method'] = 'alipay.trade.refund'; | ||
| 238 | + $this->payload['biz_content'] = json_encode($order); | ||
| 239 | + $this->payload['sign'] = Support::generateSign($this->payload); | ||
| 240 | + | ||
| 241 | + Events::dispatch(new Events\MethodCalled('Alipay', 'Refund', $this->gateway, $this->payload)); | ||
| 242 | + | ||
| 243 | + return Support::requestApi($this->payload); | ||
| 244 | + } | ||
| 245 | + | ||
| 246 | + /** | ||
| 247 | + * Cancel an order. | ||
| 248 | + * | ||
| 249 | + * @author yansongda <me@yansongda.cn> | ||
| 250 | + * | ||
| 251 | + * @param array|string $order | ||
| 252 | + * | ||
| 253 | + * @throws GatewayException | ||
| 254 | + * @throws InvalidConfigException | ||
| 255 | + * @throws InvalidSignException | ||
| 256 | + */ | ||
| 257 | + public function cancel($order): Collection | ||
| 258 | + { | ||
| 259 | + $this->payload['method'] = 'alipay.trade.cancel'; | ||
| 260 | + $this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]); | ||
| 261 | + $this->payload['sign'] = Support::generateSign($this->payload); | ||
| 262 | + | ||
| 263 | + Events::dispatch(new Events\MethodCalled('Alipay', 'Cancel', $this->gateway, $this->payload)); | ||
| 264 | + | ||
| 265 | + return Support::requestApi($this->payload); | ||
| 266 | + } | ||
| 267 | + | ||
| 268 | + /** | ||
| 269 | + * Close an order. | ||
| 270 | + * | ||
| 271 | + * @param string|array $order | ||
| 272 | + * | ||
| 273 | + * @author yansongda <me@yansongda.cn> | ||
| 274 | + * | ||
| 275 | + * @throws GatewayException | ||
| 276 | + * @throws InvalidConfigException | ||
| 277 | + * @throws InvalidSignException | ||
| 278 | + */ | ||
| 279 | + public function close($order): Collection | ||
| 280 | + { | ||
| 281 | + $this->payload['method'] = 'alipay.trade.close'; | ||
| 282 | + $this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]); | ||
| 283 | + $this->payload['sign'] = Support::generateSign($this->payload); | ||
| 284 | + | ||
| 285 | + Events::dispatch(new Events\MethodCalled('Alipay', 'Close', $this->gateway, $this->payload)); | ||
| 286 | + | ||
| 287 | + return Support::requestApi($this->payload); | ||
| 288 | + } | ||
| 289 | + | ||
| 290 | + /** | ||
| 291 | + * Download bill. | ||
| 292 | + * | ||
| 293 | + * @author yansongda <me@yansongda.cn> | ||
| 294 | + * | ||
| 295 | + * @param string|array $bill | ||
| 296 | + * | ||
| 297 | + * @throws GatewayException | ||
| 298 | + * @throws InvalidConfigException | ||
| 299 | + * @throws InvalidSignException | ||
| 300 | + */ | ||
| 301 | + public function download($bill): string | ||
| 302 | + { | ||
| 303 | + $this->payload['method'] = 'alipay.data.dataservice.bill.downloadurl.query'; | ||
| 304 | + $this->payload['biz_content'] = json_encode(is_array($bill) ? $bill : ['bill_type' => 'trade', 'bill_date' => $bill]); | ||
| 305 | + $this->payload['sign'] = Support::generateSign($this->payload); | ||
| 306 | + | ||
| 307 | + Events::dispatch(new Events\MethodCalled('Alipay', 'Download', $this->gateway, $this->payload)); | ||
| 308 | + | ||
| 309 | + $result = Support::requestApi($this->payload); | ||
| 310 | + | ||
| 311 | + return ($result instanceof Collection) ? $result->get('bill_download_url') : ''; | ||
| 312 | + } | ||
| 313 | + | ||
| 314 | + /** | ||
| 315 | + * Reply success to alipay. | ||
| 316 | + * | ||
| 317 | + * @author yansongda <me@yansongda.cn> | ||
| 318 | + */ | ||
| 319 | + public function success(): Response | ||
| 320 | + { | ||
| 321 | + Events::dispatch(new Events\MethodCalled('Alipay', 'Success', $this->gateway)); | ||
| 322 | + | ||
| 323 | + return new Response('success'); | ||
| 324 | + } | ||
| 325 | + | ||
| 326 | + /** | ||
| 327 | + * extend. | ||
| 328 | + * | ||
| 329 | + * @author yansongda <me@yansongda.cn> | ||
| 330 | + * | ||
| 331 | + * @throws GatewayException | ||
| 332 | + * @throws InvalidConfigException | ||
| 333 | + * @throws InvalidSignException | ||
| 334 | + * @throws InvalidArgumentException | ||
| 335 | + */ | ||
| 336 | + public function extend(string $method, callable $function, bool $now = true): ?Collection | ||
| 337 | + { | ||
| 338 | + if (!$now && !method_exists($this, $method)) { | ||
| 339 | + $this->extends[$method] = $function; | ||
| 340 | + | ||
| 341 | + return null; | ||
| 342 | + } | ||
| 343 | + | ||
| 344 | + $customize = $function($this->payload); | ||
| 345 | + | ||
| 346 | + if (!is_array($customize) && !($customize instanceof Collection)) { | ||
| 347 | + throw new InvalidArgumentException('Return Type Must Be Array Or Collection'); | ||
| 348 | + } | ||
| 349 | + | ||
| 350 | + Events::dispatch(new Events\MethodCalled('Alipay', 'extend', $this->gateway, $customize)); | ||
| 351 | + | ||
| 352 | + if (is_array($customize)) { | ||
| 353 | + $this->payload = $customize; | ||
| 354 | + $this->payload['sign'] = Support::generateSign($this->payload); | ||
| 355 | + | ||
| 356 | + return Support::requestApi($this->payload); | ||
| 357 | + } | ||
| 358 | + | ||
| 359 | + return $customize; | ||
| 360 | + } | ||
| 361 | + | ||
| 362 | + /** | ||
| 363 | + * Make pay gateway. | ||
| 364 | + * | ||
| 365 | + * @author yansongda <me@yansongda.cn> | ||
| 366 | + * | ||
| 367 | + * @throws InvalidGatewayException | ||
| 368 | + * | ||
| 369 | + * @return Response|Collection | ||
| 370 | + */ | ||
| 371 | + protected function makePay(string $gateway) | ||
| 372 | + { | ||
| 373 | + $app = new $gateway(); | ||
| 374 | + | ||
| 375 | + if ($app instanceof GatewayInterface) { | ||
| 376 | + return $app->pay($this->gateway, array_filter($this->payload, function ($value) { | ||
| 377 | + return '' !== $value && !is_null($value); | ||
| 378 | + })); | ||
| 379 | + } | ||
| 380 | + | ||
| 381 | + throw new InvalidGatewayException("Pay Gateway [{$gateway}] Must Be An Instance Of GatewayInterface"); | ||
| 382 | + } | ||
| 383 | + | ||
| 384 | + /** | ||
| 385 | + * makeExtend. | ||
| 386 | + * | ||
| 387 | + * @author yansongda <me@yansongda.cn> | ||
| 388 | + * | ||
| 389 | + * @throws GatewayException | ||
| 390 | + * @throws InvalidArgumentException | ||
| 391 | + * @throws InvalidConfigException | ||
| 392 | + * @throws InvalidSignException | ||
| 393 | + */ | ||
| 394 | + protected function makeExtend(string $method, array ...$params): Collection | ||
| 395 | + { | ||
| 396 | + $params = count($params) >= 1 ? $params[0] : $params; | ||
| 397 | + | ||
| 398 | + $function = $this->extends[$method]; | ||
| 399 | + | ||
| 400 | + $customize = $function($this->payload, $params); | ||
| 401 | + | ||
| 402 | + if (!is_array($customize) && !($customize instanceof Collection)) { | ||
| 403 | + throw new InvalidArgumentException('Return Type Must Be Array Or Collection'); | ||
| 404 | + } | ||
| 405 | + | ||
| 406 | + Events::dispatch(new Events\MethodCalled( | ||
| 407 | + 'Alipay', | ||
| 408 | + 'extend - '.$method, | ||
| 409 | + $this->gateway, | ||
| 410 | + is_array($customize) ? $customize : $customize->toArray() | ||
| 411 | + )); | ||
| 412 | + | ||
| 413 | + if (is_array($customize)) { | ||
| 414 | + $this->payload = $customize; | ||
| 415 | + $this->payload['sign'] = Support::generateSign($this->payload); | ||
| 416 | + | ||
| 417 | + return Support::requestApi($this->payload); | ||
| 418 | + } | ||
| 419 | + | ||
| 420 | + return $customize; | ||
| 421 | + } | ||
| 422 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Alipay; | ||
| 4 | + | ||
| 5 | +use Symfony\Component\HttpFoundation\Response; | ||
| 6 | +use Yansongda\Pay\Events; | ||
| 7 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidConfigException; | ||
| 9 | +use Yansongda\Pay\Gateways\Alipay; | ||
| 10 | + | ||
| 11 | +class AppGateway extends Gateway | ||
| 12 | +{ | ||
| 13 | + /** | ||
| 14 | + * Pay an order. | ||
| 15 | + * | ||
| 16 | + * @author yansongda <me@yansongda.cn> | ||
| 17 | + * | ||
| 18 | + * @param string $endpoint | ||
| 19 | + * | ||
| 20 | + * @throws InvalidConfigException | ||
| 21 | + * @throws InvalidArgumentException | ||
| 22 | + */ | ||
| 23 | + public function pay($endpoint, array $payload): Response | ||
| 24 | + { | ||
| 25 | + $payload['method'] = 'alipay.trade.app.pay'; | ||
| 26 | + | ||
| 27 | + $biz_array = json_decode($payload['biz_content'], true); | ||
| 28 | + if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) { | ||
| 29 | + $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid]; | ||
| 30 | + } | ||
| 31 | + $payload['biz_content'] = json_encode(array_merge($biz_array, ['product_code' => 'QUICK_MSECURITY_PAY'])); | ||
| 32 | + $payload['sign'] = Support::generateSign($payload); | ||
| 33 | + | ||
| 34 | + Events::dispatch(new Events\PayStarted('Alipay', 'App', $endpoint, $payload)); | ||
| 35 | + | ||
| 36 | + return new Response(http_build_query($payload)); | ||
| 37 | + } | ||
| 38 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Alipay; | ||
| 4 | + | ||
| 5 | +use Yansongda\Pay\Contracts\GatewayInterface; | ||
| 6 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 7 | +use Yansongda\Supports\Collection; | ||
| 8 | + | ||
| 9 | +abstract class Gateway implements GatewayInterface | ||
| 10 | +{ | ||
| 11 | + /** | ||
| 12 | + * Mode. | ||
| 13 | + * | ||
| 14 | + * @var string | ||
| 15 | + */ | ||
| 16 | + protected $mode; | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * Bootstrap. | ||
| 20 | + * | ||
| 21 | + * @author yansongda <me@yansongda.cn> | ||
| 22 | + * | ||
| 23 | + * @throws InvalidArgumentException | ||
| 24 | + */ | ||
| 25 | + public function __construct() | ||
| 26 | + { | ||
| 27 | + $this->mode = Support::getInstance()->mode; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * Pay an order. | ||
| 32 | + * | ||
| 33 | + * @author yansongda <me@yansongda.cn> | ||
| 34 | + * | ||
| 35 | + * @param string $endpoint | ||
| 36 | + * | ||
| 37 | + * @return Collection | ||
| 38 | + */ | ||
| 39 | + abstract public function pay($endpoint, array $payload); | ||
| 40 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Alipay; | ||
| 4 | + | ||
| 5 | +use Yansongda\Pay\Events; | ||
| 6 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 7 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidConfigException; | ||
| 9 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 10 | +use Yansongda\Pay\Gateways\Alipay; | ||
| 11 | +use Yansongda\Supports\Collection; | ||
| 12 | + | ||
| 13 | +class MiniGateway extends Gateway | ||
| 14 | +{ | ||
| 15 | + /** | ||
| 16 | + * Pay an order. | ||
| 17 | + * | ||
| 18 | + * @author xiaozan <i@xiaozan.me> | ||
| 19 | + * | ||
| 20 | + * @param string $endpoint | ||
| 21 | + * | ||
| 22 | + * @throws GatewayException | ||
| 23 | + * @throws InvalidArgumentException | ||
| 24 | + * @throws InvalidConfigException | ||
| 25 | + * @throws InvalidSignException | ||
| 26 | + * | ||
| 27 | + * @see https://docs.alipay.com/mini/introduce/pay | ||
| 28 | + */ | ||
| 29 | + public function pay($endpoint, array $payload): Collection | ||
| 30 | + { | ||
| 31 | + $biz_array = json_decode($payload['biz_content'], true); | ||
| 32 | + if (empty($biz_array['buyer_id'])) { | ||
| 33 | + throw new InvalidArgumentException('buyer_id required'); | ||
| 34 | + } | ||
| 35 | + if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) { | ||
| 36 | + $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid]; | ||
| 37 | + } | ||
| 38 | + $payload['biz_content'] = json_encode($biz_array); | ||
| 39 | + $payload['method'] = 'alipay.trade.create'; | ||
| 40 | + $payload['sign'] = Support::generateSign($payload); | ||
| 41 | + | ||
| 42 | + Events::dispatch(new Events\PayStarted('Alipay', 'Mini', $endpoint, $payload)); | ||
| 43 | + | ||
| 44 | + return Support::requestApi($payload); | ||
| 45 | + } | ||
| 46 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Alipay; | ||
| 4 | + | ||
| 5 | +use Yansongda\Pay\Events; | ||
| 6 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 7 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidConfigException; | ||
| 9 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 10 | +use Yansongda\Pay\Gateways\Alipay; | ||
| 11 | +use Yansongda\Supports\Collection; | ||
| 12 | + | ||
| 13 | +class PosGateway extends Gateway | ||
| 14 | +{ | ||
| 15 | + /** | ||
| 16 | + * Pay an order. | ||
| 17 | + * | ||
| 18 | + * @author yansongda <me@yansongda.cn> | ||
| 19 | + * | ||
| 20 | + * @param string $endpoint | ||
| 21 | + * | ||
| 22 | + * @throws InvalidArgumentException | ||
| 23 | + * @throws GatewayException | ||
| 24 | + * @throws InvalidConfigException | ||
| 25 | + * @throws InvalidSignException | ||
| 26 | + */ | ||
| 27 | + public function pay($endpoint, array $payload): Collection | ||
| 28 | + { | ||
| 29 | + $payload['method'] = 'alipay.trade.pay'; | ||
| 30 | + $biz_array = json_decode($payload['biz_content'], true); | ||
| 31 | + if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) { | ||
| 32 | + $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid]; | ||
| 33 | + } | ||
| 34 | + $payload['biz_content'] = json_encode(array_merge( | ||
| 35 | + $biz_array, | ||
| 36 | + [ | ||
| 37 | + 'product_code' => 'FACE_TO_FACE_PAYMENT', | ||
| 38 | + 'scene' => 'bar_code', | ||
| 39 | + ] | ||
| 40 | + )); | ||
| 41 | + $payload['sign'] = Support::generateSign($payload); | ||
| 42 | + | ||
| 43 | + Events::dispatch(new Events\PayStarted('Alipay', 'Pos', $endpoint, $payload)); | ||
| 44 | + | ||
| 45 | + return Support::requestApi($payload); | ||
| 46 | + } | ||
| 47 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Alipay; | ||
| 4 | + | ||
| 5 | +class RefundGateway | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Find. | ||
| 9 | + * | ||
| 10 | + * @author yansongda <me@yansongda.cn> | ||
| 11 | + * | ||
| 12 | + * @param $order | ||
| 13 | + */ | ||
| 14 | + public function find($order): array | ||
| 15 | + { | ||
| 16 | + return [ | ||
| 17 | + 'method' => 'alipay.trade.fastpay.refund.query', | ||
| 18 | + 'biz_content' => json_encode(is_array($order) ? $order : ['out_trade_no' => $order]), | ||
| 19 | + ]; | ||
| 20 | + } | ||
| 21 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Alipay; | ||
| 4 | + | ||
| 5 | +use Yansongda\Pay\Events; | ||
| 6 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 7 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidConfigException; | ||
| 9 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 10 | +use Yansongda\Pay\Gateways\Alipay; | ||
| 11 | +use Yansongda\Supports\Collection; | ||
| 12 | + | ||
| 13 | +class ScanGateway extends Gateway | ||
| 14 | +{ | ||
| 15 | + /** | ||
| 16 | + * Pay an order. | ||
| 17 | + * | ||
| 18 | + * @author yansongda <me@yansongda.cn> | ||
| 19 | + * | ||
| 20 | + * @param string $endpoint | ||
| 21 | + * | ||
| 22 | + * @throws GatewayException | ||
| 23 | + * @throws InvalidArgumentException | ||
| 24 | + * @throws InvalidConfigException | ||
| 25 | + * @throws InvalidSignException | ||
| 26 | + */ | ||
| 27 | + public function pay($endpoint, array $payload): Collection | ||
| 28 | + { | ||
| 29 | + $payload['method'] = 'alipay.trade.precreate'; | ||
| 30 | + $biz_array = json_decode($payload['biz_content'], true); | ||
| 31 | + if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) { | ||
| 32 | + $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid]; | ||
| 33 | + } | ||
| 34 | + $payload['biz_content'] = json_encode(array_merge($biz_array, ['product_code' => ''])); | ||
| 35 | + $payload['sign'] = Support::generateSign($payload); | ||
| 36 | + | ||
| 37 | + Events::dispatch(new Events\PayStarted('Alipay', 'Scan', $endpoint, $payload)); | ||
| 38 | + | ||
| 39 | + return Support::requestApi($payload); | ||
| 40 | + } | ||
| 41 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Alipay; | ||
| 4 | + | ||
| 5 | +use Exception; | ||
| 6 | +use Yansongda\Pay\Events; | ||
| 7 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 9 | +use Yansongda\Pay\Exceptions\InvalidConfigException; | ||
| 10 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 11 | +use Yansongda\Pay\Gateways\Alipay; | ||
| 12 | +use Yansongda\Pay\Log; | ||
| 13 | +use Yansongda\Supports\Arr; | ||
| 14 | +use Yansongda\Supports\Collection; | ||
| 15 | +use Yansongda\Supports\Config; | ||
| 16 | +use Yansongda\Supports\Str; | ||
| 17 | +use Yansongda\Supports\Traits\HasHttpRequest; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * @author yansongda <me@yansongda.cn> | ||
| 21 | + * | ||
| 22 | + * @property string app_id alipay app_id | ||
| 23 | + * @property string ali_public_key | ||
| 24 | + * @property string private_key | ||
| 25 | + * @property array http http options | ||
| 26 | + * @property string mode current mode | ||
| 27 | + * @property array log log options | ||
| 28 | + * @property string pid ali pid | ||
| 29 | + */ | ||
| 30 | +class Support | ||
| 31 | +{ | ||
| 32 | + use HasHttpRequest; | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * Alipay gateway. | ||
| 36 | + * | ||
| 37 | + * @var string | ||
| 38 | + */ | ||
| 39 | + protected $baseUri; | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * Config. | ||
| 43 | + * | ||
| 44 | + * @var Config | ||
| 45 | + */ | ||
| 46 | + protected $config; | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * Instance. | ||
| 50 | + * | ||
| 51 | + * @var Support | ||
| 52 | + */ | ||
| 53 | + private static $instance; | ||
| 54 | + | ||
| 55 | + /** | ||
| 56 | + * Bootstrap. | ||
| 57 | + * | ||
| 58 | + * @author yansongda <me@yansongda.cn> | ||
| 59 | + */ | ||
| 60 | + private function __construct(Config $config) | ||
| 61 | + { | ||
| 62 | + $this->baseUri = Alipay::URL[$config->get('mode', Alipay::MODE_NORMAL)]; | ||
| 63 | + $this->config = $config; | ||
| 64 | + | ||
| 65 | + $this->setHttpOptions(); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + /** | ||
| 69 | + * __get. | ||
| 70 | + * | ||
| 71 | + * @author yansongda <me@yansongda.cn> | ||
| 72 | + * | ||
| 73 | + * @param $key | ||
| 74 | + * | ||
| 75 | + * @return mixed|Config|null | ||
| 76 | + */ | ||
| 77 | + public function __get($key) | ||
| 78 | + { | ||
| 79 | + return $this->getConfig($key); | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + /** | ||
| 83 | + * create. | ||
| 84 | + * | ||
| 85 | + * @author yansongda <me@yansongda.cn> | ||
| 86 | + * | ||
| 87 | + * @return Support | ||
| 88 | + */ | ||
| 89 | + public static function create(Config $config) | ||
| 90 | + { | ||
| 91 | + if ('cli' === php_sapi_name() || !(self::$instance instanceof self)) { | ||
| 92 | + self::$instance = new self($config); | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + return self::$instance; | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + /** | ||
| 99 | + * getInstance. | ||
| 100 | + * | ||
| 101 | + * @author yansongda <me@yansongda.cn> | ||
| 102 | + * | ||
| 103 | + * @throws InvalidArgumentException | ||
| 104 | + * | ||
| 105 | + * @return Support | ||
| 106 | + */ | ||
| 107 | + public static function getInstance() | ||
| 108 | + { | ||
| 109 | + if (is_null(self::$instance)) { | ||
| 110 | + throw new InvalidArgumentException('You Should [Create] First Before Using'); | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + return self::$instance; | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + /** | ||
| 117 | + * clear. | ||
| 118 | + * | ||
| 119 | + * @author yansongda <me@yansongda.cn> | ||
| 120 | + */ | ||
| 121 | + public function clear() | ||
| 122 | + { | ||
| 123 | + self::$instance = null; | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + /** | ||
| 127 | + * Get Alipay API result. | ||
| 128 | + * | ||
| 129 | + * @author yansongda <me@yansongda.cn> | ||
| 130 | + * | ||
| 131 | + * @throws GatewayException | ||
| 132 | + * @throws InvalidConfigException | ||
| 133 | + * @throws InvalidSignException | ||
| 134 | + */ | ||
| 135 | + public static function requestApi(array $data): Collection | ||
| 136 | + { | ||
| 137 | + Events::dispatch(new Events\ApiRequesting('Alipay', '', self::$instance->getBaseUri(), $data)); | ||
| 138 | + | ||
| 139 | + $data = array_filter($data, function ($value) { | ||
| 140 | + return ('' == $value || is_null($value)) ? false : true; | ||
| 141 | + }); | ||
| 142 | + | ||
| 143 | + $result = json_decode(self::$instance->post('', $data), true); | ||
| 144 | + | ||
| 145 | + Events::dispatch(new Events\ApiRequested('Alipay', '', self::$instance->getBaseUri(), $result)); | ||
| 146 | + | ||
| 147 | + return self::processingApiResult($data, $result); | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + /** | ||
| 151 | + * Generate sign. | ||
| 152 | + * | ||
| 153 | + * @author yansongda <me@yansongda.cn> | ||
| 154 | + * | ||
| 155 | + * @throws InvalidConfigException | ||
| 156 | + */ | ||
| 157 | + public static function generateSign(array $params): string | ||
| 158 | + { | ||
| 159 | + $privateKey = self::$instance->private_key; | ||
| 160 | + | ||
| 161 | + if (is_null($privateKey)) { | ||
| 162 | + throw new InvalidConfigException('Missing Alipay Config -- [private_key]'); | ||
| 163 | + } | ||
| 164 | + | ||
| 165 | + if (Str::endsWith($privateKey, '.pem')) { | ||
| 166 | + $privateKey = openssl_pkey_get_private( | ||
| 167 | + Str::startsWith($privateKey, 'file://') ? $privateKey : 'file://'.$privateKey | ||
| 168 | + ); | ||
| 169 | + } else { | ||
| 170 | + $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n". | ||
| 171 | + wordwrap($privateKey, 64, "\n", true). | ||
| 172 | + "\n-----END RSA PRIVATE KEY-----"; | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + openssl_sign(self::getSignContent($params), $sign, $privateKey, OPENSSL_ALGO_SHA256); | ||
| 176 | + | ||
| 177 | + $sign = base64_encode($sign); | ||
| 178 | + | ||
| 179 | + Log::debug('Alipay Generate Sign', [$params, $sign]); | ||
| 180 | + | ||
| 181 | + if (is_resource($privateKey)) { | ||
| 182 | + openssl_free_key($privateKey); | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + return $sign; | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + /** | ||
| 189 | + * Verify sign. | ||
| 190 | + * | ||
| 191 | + * @author yansongda <me@yansonga.cn> | ||
| 192 | + * | ||
| 193 | + * @param bool $sync | ||
| 194 | + * @param string|null $sign | ||
| 195 | + * | ||
| 196 | + * @throws InvalidConfigException | ||
| 197 | + */ | ||
| 198 | + public static function verifySign(array $data, $sync = false, $sign = null): bool | ||
| 199 | + { | ||
| 200 | + $publicKey = self::$instance->ali_public_key; | ||
| 201 | + | ||
| 202 | + if (is_null($publicKey)) { | ||
| 203 | + throw new InvalidConfigException('Missing Alipay Config -- [ali_public_key]'); | ||
| 204 | + } | ||
| 205 | + | ||
| 206 | + if (Str::endsWith($publicKey, '.crt')) { | ||
| 207 | + $publicKey = file_get_contents($publicKey); | ||
| 208 | + } elseif (Str::endsWith($publicKey, '.pem')) { | ||
| 209 | + $publicKey = openssl_pkey_get_public( | ||
| 210 | + Str::startsWith($publicKey, 'file://') ? $publicKey : 'file://'.$publicKey | ||
| 211 | + ); | ||
| 212 | + } else { | ||
| 213 | + $publicKey = "-----BEGIN PUBLIC KEY-----\n". | ||
| 214 | + wordwrap($publicKey, 64, "\n", true). | ||
| 215 | + "\n-----END PUBLIC KEY-----"; | ||
| 216 | + } | ||
| 217 | + | ||
| 218 | + $sign = $sign ?? $data['sign']; | ||
| 219 | + | ||
| 220 | + $toVerify = $sync ? json_encode($data, JSON_UNESCAPED_UNICODE) : self::getSignContent($data, true); | ||
| 221 | + | ||
| 222 | + $isVerify = 1 === openssl_verify($toVerify, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256); | ||
| 223 | + | ||
| 224 | + if (is_resource($publicKey)) { | ||
| 225 | + openssl_free_key($publicKey); | ||
| 226 | + } | ||
| 227 | + | ||
| 228 | + return $isVerify; | ||
| 229 | + } | ||
| 230 | + | ||
| 231 | + /** | ||
| 232 | + * Get signContent that is to be signed. | ||
| 233 | + * | ||
| 234 | + * @author yansongda <me@yansongda.cn> | ||
| 235 | + * | ||
| 236 | + * @param bool $verify | ||
| 237 | + */ | ||
| 238 | + public static function getSignContent(array $data, $verify = false): string | ||
| 239 | + { | ||
| 240 | + ksort($data); | ||
| 241 | + | ||
| 242 | + $stringToBeSigned = ''; | ||
| 243 | + foreach ($data as $k => $v) { | ||
| 244 | + if ($verify && 'sign' != $k && 'sign_type' != $k) { | ||
| 245 | + $stringToBeSigned .= $k.'='.$v.'&'; | ||
| 246 | + } | ||
| 247 | + if (!$verify && '' !== $v && !is_null($v) && 'sign' != $k && '@' != substr($v, 0, 1)) { | ||
| 248 | + $stringToBeSigned .= $k.'='.$v.'&'; | ||
| 249 | + } | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + Log::debug('Alipay Generate Sign Content Before Trim', [$data, $stringToBeSigned]); | ||
| 253 | + | ||
| 254 | + return trim($stringToBeSigned, '&'); | ||
| 255 | + } | ||
| 256 | + | ||
| 257 | + /** | ||
| 258 | + * Convert encoding. | ||
| 259 | + * | ||
| 260 | + * @author yansongda <me@yansonga.cn> | ||
| 261 | + * | ||
| 262 | + * @param string|array $data | ||
| 263 | + * @param string $to | ||
| 264 | + * @param string $from | ||
| 265 | + */ | ||
| 266 | + public static function encoding($data, $to = 'utf-8', $from = 'gb2312'): array | ||
| 267 | + { | ||
| 268 | + return Arr::encoding((array) $data, $to, $from); | ||
| 269 | + } | ||
| 270 | + | ||
| 271 | + /** | ||
| 272 | + * Get service config. | ||
| 273 | + * | ||
| 274 | + * @author yansongda <me@yansongda.cn> | ||
| 275 | + * | ||
| 276 | + * @param string|null $key | ||
| 277 | + * @param mixed|null $default | ||
| 278 | + * | ||
| 279 | + * @return mixed|null | ||
| 280 | + */ | ||
| 281 | + public function getConfig($key = null, $default = null) | ||
| 282 | + { | ||
| 283 | + if (is_null($key)) { | ||
| 284 | + return $this->config->all(); | ||
| 285 | + } | ||
| 286 | + | ||
| 287 | + if ($this->config->has($key)) { | ||
| 288 | + return $this->config[$key]; | ||
| 289 | + } | ||
| 290 | + | ||
| 291 | + return $default; | ||
| 292 | + } | ||
| 293 | + | ||
| 294 | + /** | ||
| 295 | + * Get Base Uri. | ||
| 296 | + * | ||
| 297 | + * @author yansongda <me@yansongda.cn> | ||
| 298 | + * | ||
| 299 | + * @return string | ||
| 300 | + */ | ||
| 301 | + public function getBaseUri() | ||
| 302 | + { | ||
| 303 | + return $this->baseUri; | ||
| 304 | + } | ||
| 305 | + | ||
| 306 | + /** | ||
| 307 | + * 生成应用证书SN. | ||
| 308 | + * | ||
| 309 | + * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html | ||
| 310 | + * | ||
| 311 | + * @param $certPath | ||
| 312 | + * | ||
| 313 | + * @throws /Exception | ||
| 314 | + */ | ||
| 315 | + public static function getCertSN($certPath): string | ||
| 316 | + { | ||
| 317 | + if (!is_file($certPath)) { | ||
| 318 | + throw new Exception('unknown certPath -- [getCertSN]'); | ||
| 319 | + } | ||
| 320 | + $x509data = file_get_contents($certPath); | ||
| 321 | + if (false === $x509data) { | ||
| 322 | + throw new Exception('Alipay CertSN Error -- [getCertSN]'); | ||
| 323 | + } | ||
| 324 | + openssl_x509_read($x509data); | ||
| 325 | + $certdata = openssl_x509_parse($x509data); | ||
| 326 | + if (empty($certdata)) { | ||
| 327 | + throw new Exception('Alipay openssl_x509_parse Error -- [getCertSN]'); | ||
| 328 | + } | ||
| 329 | + $issuer_arr = []; | ||
| 330 | + foreach ($certdata['issuer'] as $key => $val) { | ||
| 331 | + $issuer_arr[] = $key.'='.$val; | ||
| 332 | + } | ||
| 333 | + $issuer = implode(',', array_reverse($issuer_arr)); | ||
| 334 | + Log::debug('getCertSN:', [$certPath, $issuer, $certdata['serialNumber']]); | ||
| 335 | + | ||
| 336 | + return md5($issuer.$certdata['serialNumber']); | ||
| 337 | + } | ||
| 338 | + | ||
| 339 | + /** | ||
| 340 | + * 生成支付宝根证书SN. | ||
| 341 | + * | ||
| 342 | + * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html | ||
| 343 | + * | ||
| 344 | + * @param $certPath | ||
| 345 | + * | ||
| 346 | + * @return string | ||
| 347 | + * | ||
| 348 | + * @throws /Exception | ||
| 349 | + */ | ||
| 350 | + public static function getRootCertSN($certPath) | ||
| 351 | + { | ||
| 352 | + if (!is_file($certPath)) { | ||
| 353 | + throw new Exception('unknown certPath -- [getRootCertSN]'); | ||
| 354 | + } | ||
| 355 | + $x509data = file_get_contents($certPath); | ||
| 356 | + if (false === $x509data) { | ||
| 357 | + throw new Exception('Alipay CertSN Error -- [getRootCertSN]'); | ||
| 358 | + } | ||
| 359 | + $kCertificateEnd = '-----END CERTIFICATE-----'; | ||
| 360 | + $certStrList = explode($kCertificateEnd, $x509data); | ||
| 361 | + $md5_arr = []; | ||
| 362 | + foreach ($certStrList as $one) { | ||
| 363 | + if (!empty(trim($one))) { | ||
| 364 | + $_x509data = $one.$kCertificateEnd; | ||
| 365 | + openssl_x509_read($_x509data); | ||
| 366 | + $_certdata = openssl_x509_parse($_x509data); | ||
| 367 | + if (in_array($_certdata['signatureTypeSN'], ['RSA-SHA256', 'RSA-SHA1'])) { | ||
| 368 | + $issuer_arr = []; | ||
| 369 | + foreach ($_certdata['issuer'] as $key => $val) { | ||
| 370 | + $issuer_arr[] = $key.'='.$val; | ||
| 371 | + } | ||
| 372 | + $_issuer = implode(',', array_reverse($issuer_arr)); | ||
| 373 | + if (0 === strpos($_certdata['serialNumber'], '0x')) { | ||
| 374 | + $serialNumber = self::bchexdec($_certdata['serialNumber']); | ||
| 375 | + } else { | ||
| 376 | + $serialNumber = $_certdata['serialNumber']; | ||
| 377 | + } | ||
| 378 | + $md5_arr[] = md5($_issuer.$serialNumber); | ||
| 379 | + Log::debug('getRootCertSN Sub:', [$certPath, $_issuer, $serialNumber]); | ||
| 380 | + } | ||
| 381 | + } | ||
| 382 | + } | ||
| 383 | + | ||
| 384 | + return implode('_', $md5_arr); | ||
| 385 | + } | ||
| 386 | + | ||
| 387 | + /** | ||
| 388 | + * processingApiResult. | ||
| 389 | + * | ||
| 390 | + * @author yansongda <me@yansongda.cn> | ||
| 391 | + * | ||
| 392 | + * @param $data | ||
| 393 | + * @param $result | ||
| 394 | + * | ||
| 395 | + * @throws GatewayException | ||
| 396 | + * @throws InvalidConfigException | ||
| 397 | + * @throws InvalidSignException | ||
| 398 | + */ | ||
| 399 | + protected static function processingApiResult($data, $result): Collection | ||
| 400 | + { | ||
| 401 | + $method = str_replace('.', '_', $data['method']).'_response'; | ||
| 402 | + | ||
| 403 | + if (!isset($result['sign']) || '10000' != $result[$method]['code']) { | ||
| 404 | + throw new GatewayException('Get Alipay API Error:'.$result[$method]['msg'].(isset($result[$method]['sub_code']) ? (' - '.$result[$method]['sub_code']) : ''), $result); | ||
| 405 | + } | ||
| 406 | + | ||
| 407 | + if (self::verifySign($result[$method], true, $result['sign'])) { | ||
| 408 | + return new Collection($result[$method]); | ||
| 409 | + } | ||
| 410 | + | ||
| 411 | + Events::dispatch(new Events\SignFailed('Alipay', '', $result)); | ||
| 412 | + | ||
| 413 | + throw new InvalidSignException('Alipay Sign Verify FAILED', $result); | ||
| 414 | + } | ||
| 415 | + | ||
| 416 | + /** | ||
| 417 | + * Set Http options. | ||
| 418 | + * | ||
| 419 | + * @author yansongda <me@yansongda.cn> | ||
| 420 | + */ | ||
| 421 | + protected function setHttpOptions(): self | ||
| 422 | + { | ||
| 423 | + if ($this->config->has('http') && is_array($this->config->get('http'))) { | ||
| 424 | + $this->config->forget('http.base_uri'); | ||
| 425 | + $this->httpOptions = $this->config->get('http'); | ||
| 426 | + } | ||
| 427 | + | ||
| 428 | + return $this; | ||
| 429 | + } | ||
| 430 | + | ||
| 431 | + /** | ||
| 432 | + * 0x转高精度数字. | ||
| 433 | + * | ||
| 434 | + * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html | ||
| 435 | + * | ||
| 436 | + * @param $hex | ||
| 437 | + * | ||
| 438 | + * @return int|string | ||
| 439 | + */ | ||
| 440 | + private static function bchexdec($hex) | ||
| 441 | + { | ||
| 442 | + $dec = 0; | ||
| 443 | + $len = strlen($hex); | ||
| 444 | + for ($i = 1; $i <= $len; ++$i) { | ||
| 445 | + if (ctype_xdigit($hex[$i - 1])) { | ||
| 446 | + $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i)))); | ||
| 447 | + } | ||
| 448 | + } | ||
| 449 | + | ||
| 450 | + return str_replace('.00', '', $dec); | ||
| 451 | + } | ||
| 452 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Alipay; | ||
| 4 | + | ||
| 5 | +use Yansongda\Pay\Contracts\GatewayInterface; | ||
| 6 | +use Yansongda\Pay\Events; | ||
| 7 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidConfigException; | ||
| 9 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 10 | +use Yansongda\Supports\Collection; | ||
| 11 | + | ||
| 12 | +class TransferGateway implements GatewayInterface | ||
| 13 | +{ | ||
| 14 | + /** | ||
| 15 | + * Pay an order. | ||
| 16 | + * | ||
| 17 | + * @author yansongda <me@yansongda.cn> | ||
| 18 | + * | ||
| 19 | + * @param string $endpoint | ||
| 20 | + * | ||
| 21 | + * @throws GatewayException | ||
| 22 | + * @throws InvalidConfigException | ||
| 23 | + * @throws InvalidSignException | ||
| 24 | + */ | ||
| 25 | + public function pay($endpoint, array $payload): Collection | ||
| 26 | + { | ||
| 27 | + $payload['method'] = 'alipay.fund.trans.uni.transfer'; | ||
| 28 | + $payload['sign'] = Support::generateSign($payload); | ||
| 29 | + | ||
| 30 | + Events::dispatch(new Events\PayStarted('Alipay', 'Transfer', $endpoint, $payload)); | ||
| 31 | + | ||
| 32 | + return Support::requestApi($payload); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * Find. | ||
| 37 | + * | ||
| 38 | + * @author yansongda <me@yansongda.cn> | ||
| 39 | + * | ||
| 40 | + * @param $order | ||
| 41 | + */ | ||
| 42 | + public function find($order): array | ||
| 43 | + { | ||
| 44 | + return [ | ||
| 45 | + 'method' => 'alipay.fund.trans.order.query', | ||
| 46 | + 'biz_content' => json_encode(is_array($order) ? $order : ['out_biz_no' => $order]), | ||
| 47 | + ]; | ||
| 48 | + } | ||
| 49 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Alipay; | ||
| 4 | + | ||
| 5 | +class WapGateway extends WebGateway | ||
| 6 | +{ | ||
| 7 | + /** | ||
| 8 | + * Get method config. | ||
| 9 | + * | ||
| 10 | + * @author yansongda <me@yansongda.cn> | ||
| 11 | + */ | ||
| 12 | + protected function getMethod(): string | ||
| 13 | + { | ||
| 14 | + return 'alipay.trade.wap.pay'; | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * Get productCode config. | ||
| 19 | + * | ||
| 20 | + * @author yansongda <me@yansongda.cn> | ||
| 21 | + */ | ||
| 22 | + protected function getProductCode(): string | ||
| 23 | + { | ||
| 24 | + return 'QUICK_WAP_WAY'; | ||
| 25 | + } | ||
| 26 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Alipay; | ||
| 4 | + | ||
| 5 | +use Symfony\Component\HttpFoundation\RedirectResponse; | ||
| 6 | +use Symfony\Component\HttpFoundation\Response; | ||
| 7 | +use Yansongda\Pay\Events; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 9 | +use Yansongda\Pay\Exceptions\InvalidConfigException; | ||
| 10 | +use Yansongda\Pay\Gateways\Alipay; | ||
| 11 | + | ||
| 12 | +class WebGateway extends Gateway | ||
| 13 | +{ | ||
| 14 | + /** | ||
| 15 | + * Pay an order. | ||
| 16 | + * | ||
| 17 | + * @author yansongda <me@yansongda.cn> | ||
| 18 | + * | ||
| 19 | + * @param string $endpoint | ||
| 20 | + * | ||
| 21 | + * @throws InvalidConfigException | ||
| 22 | + * @throws InvalidArgumentException | ||
| 23 | + */ | ||
| 24 | + public function pay($endpoint, array $payload): Response | ||
| 25 | + { | ||
| 26 | + $biz_array = json_decode($payload['biz_content'], true); | ||
| 27 | + $biz_array['product_code'] = $this->getProductCode(); | ||
| 28 | + | ||
| 29 | + $method = $biz_array['http_method'] ?? 'POST'; | ||
| 30 | + | ||
| 31 | + unset($biz_array['http_method']); | ||
| 32 | + if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) { | ||
| 33 | + $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid]; | ||
| 34 | + } | ||
| 35 | + $payload['method'] = $this->getMethod(); | ||
| 36 | + $payload['biz_content'] = json_encode($biz_array); | ||
| 37 | + $payload['sign'] = Support::generateSign($payload); | ||
| 38 | + | ||
| 39 | + Events::dispatch(new Events\PayStarted('Alipay', 'Web/Wap', $endpoint, $payload)); | ||
| 40 | + | ||
| 41 | + return $this->buildPayHtml($endpoint, $payload, $method); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + /** | ||
| 45 | + * Find. | ||
| 46 | + * | ||
| 47 | + * @author yansongda <me@yansongda.cn> | ||
| 48 | + * | ||
| 49 | + * @param $order | ||
| 50 | + */ | ||
| 51 | + public function find($order): array | ||
| 52 | + { | ||
| 53 | + return [ | ||
| 54 | + 'method' => 'alipay.trade.query', | ||
| 55 | + 'biz_content' => json_encode(is_array($order) ? $order : ['out_trade_no' => $order]), | ||
| 56 | + ]; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + /** | ||
| 60 | + * Build Html response. | ||
| 61 | + * | ||
| 62 | + * @author yansongda <me@yansongda.cn> | ||
| 63 | + * | ||
| 64 | + * @param string $endpoint | ||
| 65 | + * @param array $payload | ||
| 66 | + * @param string $method | ||
| 67 | + */ | ||
| 68 | + protected function buildPayHtml($endpoint, $payload, $method = 'POST'): Response | ||
| 69 | + { | ||
| 70 | + if ('GET' === strtoupper($method)) { | ||
| 71 | + return new RedirectResponse($endpoint.'&'.http_build_query($payload)); | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + $sHtml = "<form id='alipay_submit' name='alipay_submit' action='".$endpoint."' method='".$method."'>"; | ||
| 75 | + foreach ($payload as $key => $val) { | ||
| 76 | + $val = str_replace("'", ''', $val); | ||
| 77 | + $sHtml .= "<input type='hidden' name='".$key."' value='".$val."'/>"; | ||
| 78 | + } | ||
| 79 | + $sHtml .= "<input type='submit' value='ok' style='display:none;'></form>"; | ||
| 80 | + $sHtml .= "<script>document.forms['alipay_submit'].submit();</script>"; | ||
| 81 | + | ||
| 82 | + return new Response($sHtml); | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + /** | ||
| 86 | + * Get method config. | ||
| 87 | + * | ||
| 88 | + * @author yansongda <me@yansongda.cn> | ||
| 89 | + */ | ||
| 90 | + protected function getMethod(): string | ||
| 91 | + { | ||
| 92 | + return 'alipay.trade.page.pay'; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + /** | ||
| 96 | + * Get productCode config. | ||
| 97 | + * | ||
| 98 | + * @author yansongda <me@yansongda.cn> | ||
| 99 | + */ | ||
| 100 | + protected function getProductCode(): string | ||
| 101 | + { | ||
| 102 | + return 'FAST_INSTANT_TRADE_PAY'; | ||
| 103 | + } | ||
| 104 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways; | ||
| 4 | + | ||
| 5 | +use Exception; | ||
| 6 | +use Symfony\Component\HttpFoundation\RedirectResponse; | ||
| 7 | +use Symfony\Component\HttpFoundation\Request; | ||
| 8 | +use Symfony\Component\HttpFoundation\Response; | ||
| 9 | +use Yansongda\Pay\Contracts\GatewayApplicationInterface; | ||
| 10 | +use Yansongda\Pay\Contracts\GatewayInterface; | ||
| 11 | +use Yansongda\Pay\Events; | ||
| 12 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 13 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 14 | +use Yansongda\Pay\Exceptions\InvalidGatewayException; | ||
| 15 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 16 | +use Yansongda\Pay\Gateways\Wechat\Support; | ||
| 17 | +use Yansongda\Pay\Log; | ||
| 18 | +use Yansongda\Supports\Collection; | ||
| 19 | +use Yansongda\Supports\Config; | ||
| 20 | +use Yansongda\Supports\Str; | ||
| 21 | + | ||
| 22 | +/** | ||
| 23 | + * @method Response app(array $config) APP 支付 | ||
| 24 | + * @method Collection groupRedpack(array $config) 分裂红包 | ||
| 25 | + * @method Collection miniapp(array $config) 小程序支付 | ||
| 26 | + * @method Collection mp(array $config) 公众号支付 | ||
| 27 | + * @method Collection pos(array $config) 刷卡支付 | ||
| 28 | + * @method Collection redpack(array $config) 普通红包 | ||
| 29 | + * @method Collection scan(array $config) 扫码支付 | ||
| 30 | + * @method Collection transfer(array $config) 企业付款 | ||
| 31 | + * @method RedirectResponse web(array $config) Web 扫码支付 | ||
| 32 | + * @method RedirectResponse wap(array $config) H5 支付 | ||
| 33 | + */ | ||
| 34 | +class Wechat implements GatewayApplicationInterface | ||
| 35 | +{ | ||
| 36 | + /** | ||
| 37 | + * 普通模式. | ||
| 38 | + */ | ||
| 39 | + const MODE_NORMAL = 'normal'; | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * 沙箱模式. | ||
| 43 | + */ | ||
| 44 | + const MODE_DEV = 'dev'; | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * 香港钱包 API. | ||
| 48 | + */ | ||
| 49 | + const MODE_HK = 'hk'; | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 境外 API. | ||
| 53 | + */ | ||
| 54 | + const MODE_US = 'us'; | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * 服务商模式. | ||
| 58 | + */ | ||
| 59 | + const MODE_SERVICE = 'service'; | ||
| 60 | + | ||
| 61 | + /** | ||
| 62 | + * Const url. | ||
| 63 | + */ | ||
| 64 | + const URL = [ | ||
| 65 | + self::MODE_NORMAL => 'https://api.mch.weixin.qq.com/', | ||
| 66 | + self::MODE_DEV => 'https://api.mch.weixin.qq.com/xdc/apiv2sandbox/', | ||
| 67 | + self::MODE_HK => 'https://apihk.mch.weixin.qq.com/', | ||
| 68 | + self::MODE_SERVICE => 'https://api.mch.weixin.qq.com/', | ||
| 69 | + self::MODE_US => 'https://apius.mch.weixin.qq.com/', | ||
| 70 | + ]; | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * Wechat payload. | ||
| 74 | + * | ||
| 75 | + * @var array | ||
| 76 | + */ | ||
| 77 | + protected $payload; | ||
| 78 | + | ||
| 79 | + /** | ||
| 80 | + * Wechat gateway. | ||
| 81 | + * | ||
| 82 | + * @var string | ||
| 83 | + */ | ||
| 84 | + protected $gateway; | ||
| 85 | + | ||
| 86 | + /** | ||
| 87 | + * Bootstrap. | ||
| 88 | + * | ||
| 89 | + * @author yansongda <me@yansongda.cn> | ||
| 90 | + * | ||
| 91 | + * @throws Exception | ||
| 92 | + */ | ||
| 93 | + public function __construct(Config $config) | ||
| 94 | + { | ||
| 95 | + $this->gateway = Support::create($config)->getBaseUri(); | ||
| 96 | + $this->payload = [ | ||
| 97 | + 'appid' => $config->get('app_id', ''), | ||
| 98 | + 'mch_id' => $config->get('mch_id', ''), | ||
| 99 | + 'nonce_str' => Str::random(), | ||
| 100 | + 'notify_url' => $config->get('notify_url', ''), | ||
| 101 | + 'sign' => '', | ||
| 102 | + 'trade_type' => '', | ||
| 103 | + 'spbill_create_ip' => Request::createFromGlobals()->getClientIp(), | ||
| 104 | + ]; | ||
| 105 | + | ||
| 106 | + if ($config->get('mode', self::MODE_NORMAL) === static::MODE_SERVICE) { | ||
| 107 | + $this->payload = array_merge($this->payload, [ | ||
| 108 | + 'sub_mch_id' => $config->get('sub_mch_id'), | ||
| 109 | + 'sub_appid' => $config->get('sub_app_id', ''), | ||
| 110 | + ]); | ||
| 111 | + } | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + /** | ||
| 115 | + * Magic pay. | ||
| 116 | + * | ||
| 117 | + * @author yansongda <me@yansongda.cn> | ||
| 118 | + * | ||
| 119 | + * @param string $method | ||
| 120 | + * @param string $params | ||
| 121 | + * | ||
| 122 | + * @throws InvalidGatewayException | ||
| 123 | + * | ||
| 124 | + * @return Response|Collection | ||
| 125 | + */ | ||
| 126 | + public function __call($method, $params) | ||
| 127 | + { | ||
| 128 | + return self::pay($method, ...$params); | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + /** | ||
| 132 | + * Pay an order. | ||
| 133 | + * | ||
| 134 | + * @author yansongda <me@yansongda.cn> | ||
| 135 | + * | ||
| 136 | + * @param string $gateway | ||
| 137 | + * @param array $params | ||
| 138 | + * | ||
| 139 | + * @throws InvalidGatewayException | ||
| 140 | + * | ||
| 141 | + * @return Response|Collection | ||
| 142 | + */ | ||
| 143 | + public function pay($gateway, $params = []) | ||
| 144 | + { | ||
| 145 | + Events::dispatch(new Events\PayStarting('Wechat', $gateway, $params)); | ||
| 146 | + | ||
| 147 | + $this->payload = array_merge($this->payload, $params); | ||
| 148 | + | ||
| 149 | + $gateway = get_class($this).'\\'.Str::studly($gateway).'Gateway'; | ||
| 150 | + | ||
| 151 | + if (class_exists($gateway)) { | ||
| 152 | + return $this->makePay($gateway); | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + throw new InvalidGatewayException("Pay Gateway [{$gateway}] Not Exists"); | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + /** | ||
| 159 | + * Verify data. | ||
| 160 | + * | ||
| 161 | + * @author yansongda <me@yansongda.cn> | ||
| 162 | + * | ||
| 163 | + * @param string|null $content | ||
| 164 | + * | ||
| 165 | + * @throws InvalidSignException | ||
| 166 | + * @throws InvalidArgumentException | ||
| 167 | + */ | ||
| 168 | + public function verify($content = null, bool $refund = false): Collection | ||
| 169 | + { | ||
| 170 | + $content = $content ?? Request::createFromGlobals()->getContent(); | ||
| 171 | + | ||
| 172 | + Events::dispatch(new Events\RequestReceived('Wechat', '', [$content])); | ||
| 173 | + | ||
| 174 | + $data = Support::fromXml($content); | ||
| 175 | + if ($refund) { | ||
| 176 | + $decrypt_data = Support::decryptRefundContents($data['req_info']); | ||
| 177 | + $data = array_merge(Support::fromXml($decrypt_data), $data); | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + Log::debug('Resolved The Received Wechat Request Data', $data); | ||
| 181 | + | ||
| 182 | + if ($refund || Support::generateSign($data) === $data['sign']) { | ||
| 183 | + return new Collection($data); | ||
| 184 | + } | ||
| 185 | + | ||
| 186 | + Events::dispatch(new Events\SignFailed('Wechat', '', $data)); | ||
| 187 | + | ||
| 188 | + throw new InvalidSignException('Wechat Sign Verify FAILED', $data); | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + /** | ||
| 192 | + * Query an order. | ||
| 193 | + * | ||
| 194 | + * @author yansongda <me@yansongda.cn> | ||
| 195 | + * | ||
| 196 | + * @param string|array $order | ||
| 197 | + * | ||
| 198 | + * @throws GatewayException | ||
| 199 | + * @throws InvalidSignException | ||
| 200 | + * @throws InvalidArgumentException | ||
| 201 | + */ | ||
| 202 | + public function find($order, string $type = 'wap'): Collection | ||
| 203 | + { | ||
| 204 | + if ('wap' != $type) { | ||
| 205 | + unset($this->payload['spbill_create_ip']); | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | + $gateway = get_class($this).'\\'.Str::studly($type).'Gateway'; | ||
| 209 | + | ||
| 210 | + if (!class_exists($gateway) || !is_callable([new $gateway(), 'find'])) { | ||
| 211 | + throw new GatewayException("{$gateway} Done Not Exist Or Done Not Has FIND Method"); | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + $config = call_user_func([new $gateway(), 'find'], $order); | ||
| 215 | + | ||
| 216 | + $this->payload = Support::filterPayload($this->payload, $config['order']); | ||
| 217 | + | ||
| 218 | + Events::dispatch(new Events\MethodCalled('Wechat', 'Find', $this->gateway, $this->payload)); | ||
| 219 | + | ||
| 220 | + return Support::requestApi( | ||
| 221 | + $config['endpoint'], | ||
| 222 | + $this->payload, | ||
| 223 | + $config['cert'] | ||
| 224 | + ); | ||
| 225 | + } | ||
| 226 | + | ||
| 227 | + /** | ||
| 228 | + * Refund an order. | ||
| 229 | + * | ||
| 230 | + * @author yansongda <me@yansongda.cn> | ||
| 231 | + * | ||
| 232 | + * @throws GatewayException | ||
| 233 | + * @throws InvalidSignException | ||
| 234 | + * @throws InvalidArgumentException | ||
| 235 | + */ | ||
| 236 | + public function refund(array $order): Collection | ||
| 237 | + { | ||
| 238 | + $this->payload = Support::filterPayload($this->payload, $order, true); | ||
| 239 | + | ||
| 240 | + Events::dispatch(new Events\MethodCalled('Wechat', 'Refund', $this->gateway, $this->payload)); | ||
| 241 | + | ||
| 242 | + return Support::requestApi( | ||
| 243 | + 'secapi/pay/refund', | ||
| 244 | + $this->payload, | ||
| 245 | + true | ||
| 246 | + ); | ||
| 247 | + } | ||
| 248 | + | ||
| 249 | + /** | ||
| 250 | + * Cancel an order. | ||
| 251 | + * | ||
| 252 | + * @author yansongda <me@yansongda.cn> | ||
| 253 | + * | ||
| 254 | + * @param array $order | ||
| 255 | + * | ||
| 256 | + * @throws GatewayException | ||
| 257 | + * @throws InvalidSignException | ||
| 258 | + * @throws InvalidArgumentException | ||
| 259 | + */ | ||
| 260 | + public function cancel($order): Collection | ||
| 261 | + { | ||
| 262 | + unset($this->payload['spbill_create_ip']); | ||
| 263 | + | ||
| 264 | + $this->payload = Support::filterPayload($this->payload, $order); | ||
| 265 | + | ||
| 266 | + Events::dispatch(new Events\MethodCalled('Wechat', 'Cancel', $this->gateway, $this->payload)); | ||
| 267 | + | ||
| 268 | + return Support::requestApi( | ||
| 269 | + 'secapi/pay/reverse', | ||
| 270 | + $this->payload, | ||
| 271 | + true | ||
| 272 | + ); | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + /** | ||
| 276 | + * Close an order. | ||
| 277 | + * | ||
| 278 | + * @author yansongda <me@yansongda.cn> | ||
| 279 | + * | ||
| 280 | + * @param string|array $order | ||
| 281 | + * | ||
| 282 | + * @throws GatewayException | ||
| 283 | + * @throws InvalidSignException | ||
| 284 | + * @throws InvalidArgumentException | ||
| 285 | + */ | ||
| 286 | + public function close($order): Collection | ||
| 287 | + { | ||
| 288 | + unset($this->payload['spbill_create_ip']); | ||
| 289 | + | ||
| 290 | + $this->payload = Support::filterPayload($this->payload, $order); | ||
| 291 | + | ||
| 292 | + Events::dispatch(new Events\MethodCalled('Wechat', 'Close', $this->gateway, $this->payload)); | ||
| 293 | + | ||
| 294 | + return Support::requestApi('pay/closeorder', $this->payload); | ||
| 295 | + } | ||
| 296 | + | ||
| 297 | + /** | ||
| 298 | + * Echo success to server. | ||
| 299 | + * | ||
| 300 | + * @author yansongda <me@yansongda.cn> | ||
| 301 | + * | ||
| 302 | + * @throws InvalidArgumentException | ||
| 303 | + */ | ||
| 304 | + public function success(): Response | ||
| 305 | + { | ||
| 306 | + Events::dispatch(new Events\MethodCalled('Wechat', 'Success', $this->gateway)); | ||
| 307 | + | ||
| 308 | + return new Response( | ||
| 309 | + Support::toXml(['return_code' => 'SUCCESS', 'return_msg' => 'OK']), | ||
| 310 | + 200, | ||
| 311 | + ['Content-Type' => 'application/xml'] | ||
| 312 | + ); | ||
| 313 | + } | ||
| 314 | + | ||
| 315 | + /** | ||
| 316 | + * Download the bill. | ||
| 317 | + * | ||
| 318 | + * @author yansongda <me@yansongda.cn> | ||
| 319 | + * | ||
| 320 | + * @throws GatewayException | ||
| 321 | + * @throws InvalidArgumentException | ||
| 322 | + */ | ||
| 323 | + public function download(array $params): string | ||
| 324 | + { | ||
| 325 | + unset($this->payload['spbill_create_ip']); | ||
| 326 | + | ||
| 327 | + $this->payload = Support::filterPayload($this->payload, $params, true); | ||
| 328 | + | ||
| 329 | + Events::dispatch(new Events\MethodCalled('Wechat', 'Download', $this->gateway, $this->payload)); | ||
| 330 | + | ||
| 331 | + $result = Support::getInstance()->post( | ||
| 332 | + 'pay/downloadbill', | ||
| 333 | + Support::getInstance()->toXml($this->payload) | ||
| 334 | + ); | ||
| 335 | + | ||
| 336 | + if (is_array($result)) { | ||
| 337 | + throw new GatewayException('Get Wechat API Error: '.$result['return_msg'], $result); | ||
| 338 | + } | ||
| 339 | + | ||
| 340 | + return $result; | ||
| 341 | + } | ||
| 342 | + | ||
| 343 | + /** | ||
| 344 | + * Make pay gateway. | ||
| 345 | + * | ||
| 346 | + * @author yansongda <me@yansongda.cn> | ||
| 347 | + * | ||
| 348 | + * @param string $gateway | ||
| 349 | + * | ||
| 350 | + * @throws InvalidGatewayException | ||
| 351 | + * | ||
| 352 | + * @return Response|Collection | ||
| 353 | + */ | ||
| 354 | + protected function makePay($gateway) | ||
| 355 | + { | ||
| 356 | + $app = new $gateway(); | ||
| 357 | + | ||
| 358 | + if ($app instanceof GatewayInterface) { | ||
| 359 | + return $app->pay($this->gateway, array_filter($this->payload, function ($value) { | ||
| 360 | + return '' !== $value && !is_null($value); | ||
| 361 | + })); | ||
| 362 | + } | ||
| 363 | + | ||
| 364 | + throw new InvalidGatewayException("Pay Gateway [{$gateway}] Must Be An Instance Of GatewayInterface"); | ||
| 365 | + } | ||
| 366 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Wechat; | ||
| 4 | + | ||
| 5 | +use Exception; | ||
| 6 | +use Symfony\Component\HttpFoundation\JsonResponse; | ||
| 7 | +use Symfony\Component\HttpFoundation\Response; | ||
| 8 | +use Yansongda\Pay\Events; | ||
| 9 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 10 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 11 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 12 | +use Yansongda\Pay\Gateways\Wechat; | ||
| 13 | +use Yansongda\Supports\Str; | ||
| 14 | + | ||
| 15 | +class AppGateway extends Gateway | ||
| 16 | +{ | ||
| 17 | + /** | ||
| 18 | + * Pay an order. | ||
| 19 | + * | ||
| 20 | + * @author yansongda <me@yansongda.cn> | ||
| 21 | + * | ||
| 22 | + * @param string $endpoint | ||
| 23 | + * | ||
| 24 | + * @throws GatewayException | ||
| 25 | + * @throws InvalidArgumentException | ||
| 26 | + * @throws InvalidSignException | ||
| 27 | + * @throws Exception | ||
| 28 | + */ | ||
| 29 | + public function pay($endpoint, array $payload): Response | ||
| 30 | + { | ||
| 31 | + $payload['appid'] = Support::getInstance()->appid; | ||
| 32 | + $payload['trade_type'] = $this->getTradeType(); | ||
| 33 | + | ||
| 34 | + if (Wechat::MODE_SERVICE === $this->mode) { | ||
| 35 | + $payload['sub_appid'] = Support::getInstance()->sub_appid; | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + $pay_request = [ | ||
| 39 | + 'appid' => Wechat::MODE_SERVICE === $this->mode ? $payload['sub_appid'] : $payload['appid'], | ||
| 40 | + 'partnerid' => Wechat::MODE_SERVICE === $this->mode ? $payload['sub_mch_id'] : $payload['mch_id'], | ||
| 41 | + 'prepayid' => $this->preOrder($payload)->get('prepay_id'), | ||
| 42 | + 'timestamp' => strval(time()), | ||
| 43 | + 'noncestr' => Str::random(), | ||
| 44 | + 'package' => 'Sign=WXPay', | ||
| 45 | + ]; | ||
| 46 | + $pay_request['sign'] = Support::generateSign($pay_request); | ||
| 47 | + | ||
| 48 | + Events::dispatch(new Events\PayStarted('Wechat', 'App', $endpoint, $pay_request)); | ||
| 49 | + | ||
| 50 | + return new JsonResponse($pay_request); | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + /** | ||
| 54 | + * Get trade type config. | ||
| 55 | + * | ||
| 56 | + * @author yansongda <me@yansongda.cn> | ||
| 57 | + */ | ||
| 58 | + protected function getTradeType(): string | ||
| 59 | + { | ||
| 60 | + return 'APP'; | ||
| 61 | + } | ||
| 62 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Wechat; | ||
| 4 | + | ||
| 5 | +use Yansongda\Pay\Contracts\GatewayInterface; | ||
| 6 | +use Yansongda\Pay\Events; | ||
| 7 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 9 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 10 | +use Yansongda\Supports\Collection; | ||
| 11 | + | ||
| 12 | +abstract class Gateway implements GatewayInterface | ||
| 13 | +{ | ||
| 14 | + /** | ||
| 15 | + * Mode. | ||
| 16 | + * | ||
| 17 | + * @var string | ||
| 18 | + */ | ||
| 19 | + protected $mode; | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * Bootstrap. | ||
| 23 | + * | ||
| 24 | + * @author yansongda <me@yansongda.cn> | ||
| 25 | + * | ||
| 26 | + * @throws InvalidArgumentException | ||
| 27 | + */ | ||
| 28 | + public function __construct() | ||
| 29 | + { | ||
| 30 | + $this->mode = Support::getInstance()->mode; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + /** | ||
| 34 | + * Pay an order. | ||
| 35 | + * | ||
| 36 | + * @author yansongda <me@yansongda.cn> | ||
| 37 | + * | ||
| 38 | + * @param string $endpoint | ||
| 39 | + * | ||
| 40 | + * @return Collection | ||
| 41 | + */ | ||
| 42 | + abstract public function pay($endpoint, array $payload); | ||
| 43 | + | ||
| 44 | + /** | ||
| 45 | + * Find. | ||
| 46 | + * | ||
| 47 | + * @author yansongda <me@yansongda.cn> | ||
| 48 | + * | ||
| 49 | + * @param string|array $order | ||
| 50 | + */ | ||
| 51 | + public function find($order): array | ||
| 52 | + { | ||
| 53 | + return [ | ||
| 54 | + 'endpoint' => 'pay/orderquery', | ||
| 55 | + 'order' => is_array($order) ? $order : ['out_trade_no' => $order], | ||
| 56 | + 'cert' => false, | ||
| 57 | + ]; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + /** | ||
| 61 | + * Get trade type config. | ||
| 62 | + * | ||
| 63 | + * @author yansongda <me@yansongda.cn> | ||
| 64 | + * | ||
| 65 | + * @return string | ||
| 66 | + */ | ||
| 67 | + abstract protected function getTradeType(); | ||
| 68 | + | ||
| 69 | + /** | ||
| 70 | + * Schedule an order. | ||
| 71 | + * | ||
| 72 | + * @author yansongda <me@yansongda.cn> | ||
| 73 | + * | ||
| 74 | + * @param array $payload | ||
| 75 | + * | ||
| 76 | + * @throws GatewayException | ||
| 77 | + * @throws InvalidArgumentException | ||
| 78 | + * @throws InvalidSignException | ||
| 79 | + */ | ||
| 80 | + protected function preOrder($payload): Collection | ||
| 81 | + { | ||
| 82 | + $payload['sign'] = Support::generateSign($payload); | ||
| 83 | + | ||
| 84 | + Events::dispatch(new Events\MethodCalled('Wechat', 'PreOrder', '', $payload)); | ||
| 85 | + | ||
| 86 | + return Support::requestApi('pay/unifiedorder', $payload); | ||
| 87 | + } | ||
| 88 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Wechat; | ||
| 4 | + | ||
| 5 | +use Yansongda\Pay\Events; | ||
| 6 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 7 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 9 | +use Yansongda\Pay\Gateways\Wechat; | ||
| 10 | +use Yansongda\Supports\Collection; | ||
| 11 | + | ||
| 12 | +class GroupRedpackGateway extends Gateway | ||
| 13 | +{ | ||
| 14 | + /** | ||
| 15 | + * Pay an order. | ||
| 16 | + * | ||
| 17 | + * @author yansongda <me@yansongda.cn> | ||
| 18 | + * | ||
| 19 | + * @param string $endpoint | ||
| 20 | + * | ||
| 21 | + * @throws GatewayException | ||
| 22 | + * @throws InvalidArgumentException | ||
| 23 | + * @throws InvalidSignException | ||
| 24 | + */ | ||
| 25 | + public function pay($endpoint, array $payload): Collection | ||
| 26 | + { | ||
| 27 | + $payload['wxappid'] = $payload['appid']; | ||
| 28 | + $payload['amt_type'] = 'ALL_RAND'; | ||
| 29 | + | ||
| 30 | + if (Wechat::MODE_SERVICE === $this->mode) { | ||
| 31 | + $payload['msgappid'] = $payload['appid']; | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + unset($payload['appid'], $payload['trade_type'], | ||
| 35 | + $payload['notify_url'], $payload['spbill_create_ip']); | ||
| 36 | + | ||
| 37 | + $payload['sign'] = Support::generateSign($payload); | ||
| 38 | + | ||
| 39 | + Events::dispatch(new Events\PayStarted('Wechat', 'Group Redpack', $endpoint, $payload)); | ||
| 40 | + | ||
| 41 | + return Support::requestApi( | ||
| 42 | + 'mmpaymkttransfers/sendgroupredpack', | ||
| 43 | + $payload, | ||
| 44 | + true | ||
| 45 | + ); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * Get trade type config. | ||
| 50 | + * | ||
| 51 | + * @author yansongda <me@yansongda.cn> | ||
| 52 | + */ | ||
| 53 | + protected function getTradeType(): string | ||
| 54 | + { | ||
| 55 | + return ''; | ||
| 56 | + } | ||
| 57 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Wechat; | ||
| 4 | + | ||
| 5 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 6 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 7 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 8 | +use Yansongda\Pay\Gateways\Wechat; | ||
| 9 | +use Yansongda\Supports\Collection; | ||
| 10 | + | ||
| 11 | +class MiniappGateway extends MpGateway | ||
| 12 | +{ | ||
| 13 | + /** | ||
| 14 | + * Pay an order. | ||
| 15 | + * | ||
| 16 | + * @author yansongda <me@yansongda.cn> | ||
| 17 | + * | ||
| 18 | + * @param string $endpoint | ||
| 19 | + * | ||
| 20 | + * @throws GatewayException | ||
| 21 | + * @throws InvalidArgumentException | ||
| 22 | + * @throws InvalidSignException | ||
| 23 | + */ | ||
| 24 | + public function pay($endpoint, array $payload): Collection | ||
| 25 | + { | ||
| 26 | + $payload['appid'] = Support::getInstance()->miniapp_id; | ||
| 27 | + | ||
| 28 | + if (Wechat::MODE_SERVICE === $this->mode) { | ||
| 29 | + $payload['sub_appid'] = Support::getInstance()->sub_miniapp_id; | ||
| 30 | + $this->payRequestUseSubAppId = true; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + return parent::pay($endpoint, $payload); | ||
| 34 | + } | ||
| 35 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Wechat; | ||
| 4 | + | ||
| 5 | +use Exception; | ||
| 6 | +use Yansongda\Pay\Events; | ||
| 7 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 9 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 10 | +use Yansongda\Supports\Collection; | ||
| 11 | +use Yansongda\Supports\Str; | ||
| 12 | + | ||
| 13 | +class MpGateway extends Gateway | ||
| 14 | +{ | ||
| 15 | + /** | ||
| 16 | + * @var bool | ||
| 17 | + */ | ||
| 18 | + protected $payRequestUseSubAppId = false; | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * Pay an order. | ||
| 22 | + * | ||
| 23 | + * @author yansongda <me@yansongda.cn> | ||
| 24 | + * | ||
| 25 | + * @param string $endpoint | ||
| 26 | + * | ||
| 27 | + * @throws GatewayException | ||
| 28 | + * @throws InvalidArgumentException | ||
| 29 | + * @throws InvalidSignException | ||
| 30 | + * @throws Exception | ||
| 31 | + */ | ||
| 32 | + public function pay($endpoint, array $payload): Collection | ||
| 33 | + { | ||
| 34 | + $payload['trade_type'] = $this->getTradeType(); | ||
| 35 | + | ||
| 36 | + $pay_request = [ | ||
| 37 | + 'appId' => !$this->payRequestUseSubAppId ? $payload['appid'] : $payload['sub_appid'], | ||
| 38 | + 'timeStamp' => strval(time()), | ||
| 39 | + 'nonceStr' => Str::random(), | ||
| 40 | + 'package' => 'prepay_id='.$this->preOrder($payload)->get('prepay_id'), | ||
| 41 | + 'signType' => 'MD5', | ||
| 42 | + ]; | ||
| 43 | + $pay_request['paySign'] = Support::generateSign($pay_request); | ||
| 44 | + | ||
| 45 | + Events::dispatch(new Events\PayStarted('Wechat', 'JSAPI', $endpoint, $pay_request)); | ||
| 46 | + | ||
| 47 | + return new Collection($pay_request); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + /** | ||
| 51 | + * Get trade type config. | ||
| 52 | + * | ||
| 53 | + * @author yansongda <me@yansongda.cn> | ||
| 54 | + */ | ||
| 55 | + protected function getTradeType(): string | ||
| 56 | + { | ||
| 57 | + return 'JSAPI'; | ||
| 58 | + } | ||
| 59 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Wechat; | ||
| 4 | + | ||
| 5 | +use Yansongda\Pay\Events; | ||
| 6 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 7 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 9 | +use Yansongda\Supports\Collection; | ||
| 10 | + | ||
| 11 | +class PosGateway extends Gateway | ||
| 12 | +{ | ||
| 13 | + /** | ||
| 14 | + * Pay an order. | ||
| 15 | + * | ||
| 16 | + * @author yansongda <me@yansongda.cn> | ||
| 17 | + * | ||
| 18 | + * @param string $endpoint | ||
| 19 | + * | ||
| 20 | + * @throws GatewayException | ||
| 21 | + * @throws InvalidArgumentException | ||
| 22 | + * @throws InvalidSignException | ||
| 23 | + */ | ||
| 24 | + public function pay($endpoint, array $payload): Collection | ||
| 25 | + { | ||
| 26 | + unset($payload['trade_type'], $payload['notify_url']); | ||
| 27 | + | ||
| 28 | + $payload['sign'] = Support::generateSign($payload); | ||
| 29 | + | ||
| 30 | + Events::dispatch(new Events\PayStarted('Wechat', 'Pos', $endpoint, $payload)); | ||
| 31 | + | ||
| 32 | + return Support::requestApi('pay/micropay', $payload); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * Get trade type config. | ||
| 37 | + * | ||
| 38 | + * @author yansongda <me@yansongda.cn> | ||
| 39 | + */ | ||
| 40 | + protected function getTradeType(): string | ||
| 41 | + { | ||
| 42 | + return 'MICROPAY'; | ||
| 43 | + } | ||
| 44 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Wechat; | ||
| 4 | + | ||
| 5 | +use Symfony\Component\HttpFoundation\Request; | ||
| 6 | +use Yansongda\Pay\Events; | ||
| 7 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 9 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 10 | +use Yansongda\Pay\Gateways\Wechat; | ||
| 11 | +use Yansongda\Supports\Collection; | ||
| 12 | + | ||
| 13 | +class RedpackGateway extends Gateway | ||
| 14 | +{ | ||
| 15 | + /** | ||
| 16 | + * Pay an order. | ||
| 17 | + * | ||
| 18 | + * @author yansongda <me@yansongda.cn> | ||
| 19 | + * | ||
| 20 | + * @param string $endpoint | ||
| 21 | + * | ||
| 22 | + * @throws GatewayException | ||
| 23 | + * @throws InvalidArgumentException | ||
| 24 | + * @throws InvalidSignException | ||
| 25 | + */ | ||
| 26 | + public function pay($endpoint, array $payload): Collection | ||
| 27 | + { | ||
| 28 | + $payload['wxappid'] = $payload['appid']; | ||
| 29 | + | ||
| 30 | + if ('cli' !== php_sapi_name()) { | ||
| 31 | + $payload['client_ip'] = Request::createFromGlobals()->server->get('SERVER_ADDR'); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + if (Wechat::MODE_SERVICE === $this->mode) { | ||
| 35 | + $payload['msgappid'] = $payload['appid']; | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + unset($payload['appid'], $payload['trade_type'], | ||
| 39 | + $payload['notify_url'], $payload['spbill_create_ip']); | ||
| 40 | + | ||
| 41 | + $payload['sign'] = Support::generateSign($payload); | ||
| 42 | + | ||
| 43 | + Events::dispatch(new Events\PayStarted('Wechat', 'Redpack', $endpoint, $payload)); | ||
| 44 | + | ||
| 45 | + return Support::requestApi( | ||
| 46 | + 'mmpaymkttransfers/sendredpack', | ||
| 47 | + $payload, | ||
| 48 | + true | ||
| 49 | + ); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + /** | ||
| 53 | + * Get trade type config. | ||
| 54 | + * | ||
| 55 | + * @author yansongda <me@yansongda.cn> | ||
| 56 | + */ | ||
| 57 | + protected function getTradeType(): string | ||
| 58 | + { | ||
| 59 | + return ''; | ||
| 60 | + } | ||
| 61 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Wechat; | ||
| 4 | + | ||
| 5 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 6 | + | ||
| 7 | +class RefundGateway extends Gateway | ||
| 8 | +{ | ||
| 9 | + /** | ||
| 10 | + * Find. | ||
| 11 | + * | ||
| 12 | + * @author yansongda <me@yansongda.cn> | ||
| 13 | + * | ||
| 14 | + * @param $order | ||
| 15 | + */ | ||
| 16 | + public function find($order): array | ||
| 17 | + { | ||
| 18 | + return [ | ||
| 19 | + 'endpoint' => 'pay/refundquery', | ||
| 20 | + 'order' => is_array($order) ? $order : ['out_trade_no' => $order], | ||
| 21 | + 'cert' => false, | ||
| 22 | + ]; | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * Pay an order. | ||
| 27 | + * | ||
| 28 | + * @author yansongda <me@yansongda.cn> | ||
| 29 | + * | ||
| 30 | + * @param string $endpoint | ||
| 31 | + * | ||
| 32 | + * @throws InvalidArgumentException | ||
| 33 | + */ | ||
| 34 | + public function pay($endpoint, array $payload) | ||
| 35 | + { | ||
| 36 | + throw new InvalidArgumentException('Not Support Refund In Pay'); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + /** | ||
| 40 | + * Get trade type config. | ||
| 41 | + * | ||
| 42 | + * @author yansongda <me@yansongda.cn> | ||
| 43 | + * | ||
| 44 | + * @throws InvalidArgumentException | ||
| 45 | + */ | ||
| 46 | + protected function getTradeType() | ||
| 47 | + { | ||
| 48 | + throw new InvalidArgumentException('Not Support Refund In Pay'); | ||
| 49 | + } | ||
| 50 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Wechat; | ||
| 4 | + | ||
| 5 | +use Symfony\Component\HttpFoundation\Request; | ||
| 6 | +use Yansongda\Pay\Events; | ||
| 7 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 9 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 10 | +use Yansongda\Supports\Collection; | ||
| 11 | + | ||
| 12 | +class ScanGateway extends Gateway | ||
| 13 | +{ | ||
| 14 | + /** | ||
| 15 | + * Pay an order. | ||
| 16 | + * | ||
| 17 | + * @author yansongda <me@yansongda.cn> | ||
| 18 | + * | ||
| 19 | + * @param string $endpoint | ||
| 20 | + * | ||
| 21 | + * @throws GatewayException | ||
| 22 | + * @throws InvalidArgumentException | ||
| 23 | + * @throws InvalidSignException | ||
| 24 | + */ | ||
| 25 | + public function pay($endpoint, array $payload): Collection | ||
| 26 | + { | ||
| 27 | + $payload['spbill_create_ip'] = Request::createFromGlobals()->server->get('SERVER_ADDR'); | ||
| 28 | + $payload['trade_type'] = $this->getTradeType(); | ||
| 29 | + | ||
| 30 | + Events::dispatch(new Events\PayStarted('Wechat', 'Scan', $endpoint, $payload)); | ||
| 31 | + | ||
| 32 | + return $this->preOrder($payload); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * Get trade type config. | ||
| 37 | + * | ||
| 38 | + * @author yansongda <me@yansongda.cn> | ||
| 39 | + */ | ||
| 40 | + protected function getTradeType(): string | ||
| 41 | + { | ||
| 42 | + return 'NATIVE'; | ||
| 43 | + } | ||
| 44 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Wechat; | ||
| 4 | + | ||
| 5 | +use Exception; | ||
| 6 | +use Yansongda\Pay\Events; | ||
| 7 | +use Yansongda\Pay\Exceptions\BusinessException; | ||
| 8 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 9 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 10 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 11 | +use Yansongda\Pay\Gateways\Wechat; | ||
| 12 | +use Yansongda\Pay\Log; | ||
| 13 | +use Yansongda\Supports\Collection; | ||
| 14 | +use Yansongda\Supports\Config; | ||
| 15 | +use Yansongda\Supports\Str; | ||
| 16 | +use Yansongda\Supports\Traits\HasHttpRequest; | ||
| 17 | + | ||
| 18 | +/** | ||
| 19 | + * @author yansongda <me@yansongda.cn> | ||
| 20 | + * | ||
| 21 | + * @property string appid | ||
| 22 | + * @property string app_id | ||
| 23 | + * @property string miniapp_id | ||
| 24 | + * @property string sub_appid | ||
| 25 | + * @property string sub_app_id | ||
| 26 | + * @property string sub_miniapp_id | ||
| 27 | + * @property string mch_id | ||
| 28 | + * @property string sub_mch_id | ||
| 29 | + * @property string key | ||
| 30 | + * @property string return_url | ||
| 31 | + * @property string cert_client | ||
| 32 | + * @property string cert_key | ||
| 33 | + * @property array log | ||
| 34 | + * @property array http | ||
| 35 | + * @property string mode | ||
| 36 | + */ | ||
| 37 | +class Support | ||
| 38 | +{ | ||
| 39 | + use HasHttpRequest; | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * Wechat gateway. | ||
| 43 | + * | ||
| 44 | + * @var string | ||
| 45 | + */ | ||
| 46 | + protected $baseUri; | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * Config. | ||
| 50 | + * | ||
| 51 | + * @var Config | ||
| 52 | + */ | ||
| 53 | + protected $config; | ||
| 54 | + | ||
| 55 | + /** | ||
| 56 | + * Instance. | ||
| 57 | + * | ||
| 58 | + * @var Support | ||
| 59 | + */ | ||
| 60 | + private static $instance; | ||
| 61 | + | ||
| 62 | + /** | ||
| 63 | + * Bootstrap. | ||
| 64 | + * | ||
| 65 | + * @author yansongda <me@yansongda.cn> | ||
| 66 | + */ | ||
| 67 | + private function __construct(Config $config) | ||
| 68 | + { | ||
| 69 | + $this->baseUri = Wechat::URL[$config->get('mode', Wechat::MODE_NORMAL)]; | ||
| 70 | + $this->config = $config; | ||
| 71 | + | ||
| 72 | + $this->setHttpOptions(); | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * __get. | ||
| 77 | + * | ||
| 78 | + * @author yansongda <me@yansongda.cn> | ||
| 79 | + * | ||
| 80 | + * @param $key | ||
| 81 | + * | ||
| 82 | + * @return mixed|Config|null | ||
| 83 | + */ | ||
| 84 | + public function __get($key) | ||
| 85 | + { | ||
| 86 | + return $this->getConfig($key); | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + /** | ||
| 90 | + * create. | ||
| 91 | + * | ||
| 92 | + * @author yansongda <me@yansongda.cn> | ||
| 93 | + * | ||
| 94 | + * @throws GatewayException | ||
| 95 | + * @throws InvalidArgumentException | ||
| 96 | + * @throws InvalidSignException | ||
| 97 | + * | ||
| 98 | + * @return Support | ||
| 99 | + */ | ||
| 100 | + public static function create(Config $config) | ||
| 101 | + { | ||
| 102 | + if ('cli' === php_sapi_name() || !(self::$instance instanceof self)) { | ||
| 103 | + self::$instance = new self($config); | ||
| 104 | + | ||
| 105 | + self::setDevKey(); | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + return self::$instance; | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + /** | ||
| 112 | + * getInstance. | ||
| 113 | + * | ||
| 114 | + * @author yansongda <me@yansongda.cn> | ||
| 115 | + * | ||
| 116 | + * @throws InvalidArgumentException | ||
| 117 | + * | ||
| 118 | + * @return Support | ||
| 119 | + */ | ||
| 120 | + public static function getInstance() | ||
| 121 | + { | ||
| 122 | + if (is_null(self::$instance)) { | ||
| 123 | + throw new InvalidArgumentException('You Should [Create] First Before Using'); | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + return self::$instance; | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + /** | ||
| 130 | + * clear. | ||
| 131 | + * | ||
| 132 | + * @author yansongda <me@yansongda.cn> | ||
| 133 | + */ | ||
| 134 | + public static function clear() | ||
| 135 | + { | ||
| 136 | + self::$instance = null; | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + /** | ||
| 140 | + * Request wechat api. | ||
| 141 | + * | ||
| 142 | + * @author yansongda <me@yansongda.cn> | ||
| 143 | + * | ||
| 144 | + * @param string $endpoint | ||
| 145 | + * @param array $data | ||
| 146 | + * @param bool $cert | ||
| 147 | + * | ||
| 148 | + * @throws GatewayException | ||
| 149 | + * @throws InvalidArgumentException | ||
| 150 | + * @throws InvalidSignException | ||
| 151 | + */ | ||
| 152 | + public static function requestApi($endpoint, $data, $cert = false): Collection | ||
| 153 | + { | ||
| 154 | + Events::dispatch(new Events\ApiRequesting('Wechat', '', self::$instance->getBaseUri().$endpoint, $data)); | ||
| 155 | + | ||
| 156 | + $result = self::$instance->post( | ||
| 157 | + $endpoint, | ||
| 158 | + self::toXml($data), | ||
| 159 | + $cert ? [ | ||
| 160 | + 'cert' => self::$instance->cert_client, | ||
| 161 | + 'ssl_key' => self::$instance->cert_key, | ||
| 162 | + ] : [ | ||
| 163 | + 'headers' => [ | ||
| 164 | + 'Content-Type'=>'application/xml', | ||
| 165 | + ], | ||
| 166 | + ] | ||
| 167 | + ); | ||
| 168 | + $result = is_array($result) ? $result : self::fromXml($result); | ||
| 169 | + | ||
| 170 | + Events::dispatch(new Events\ApiRequested('Wechat', '', self::$instance->getBaseUri().$endpoint, $result)); | ||
| 171 | + | ||
| 172 | + return self::processingApiResult($endpoint, $result); | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + /** | ||
| 176 | + * Filter payload. | ||
| 177 | + * | ||
| 178 | + * @author yansongda <me@yansongda.cn> | ||
| 179 | + * | ||
| 180 | + * @param array $payload | ||
| 181 | + * @param array|string $params | ||
| 182 | + * @param bool $preserve_notify_url | ||
| 183 | + * | ||
| 184 | + * @throws InvalidArgumentException | ||
| 185 | + */ | ||
| 186 | + public static function filterPayload($payload, $params, $preserve_notify_url = false): array | ||
| 187 | + { | ||
| 188 | + $type = self::getTypeName($params['type'] ?? ''); | ||
| 189 | + | ||
| 190 | + $payload = array_merge( | ||
| 191 | + $payload, | ||
| 192 | + is_array($params) ? $params : ['out_trade_no' => $params] | ||
| 193 | + ); | ||
| 194 | + $payload['appid'] = self::$instance->getConfig($type, ''); | ||
| 195 | + | ||
| 196 | + if (Wechat::MODE_SERVICE === self::$instance->getConfig('mode', Wechat::MODE_NORMAL)) { | ||
| 197 | + $payload['sub_appid'] = self::$instance->getConfig('sub_'.$type, ''); | ||
| 198 | + } | ||
| 199 | + | ||
| 200 | + unset($payload['trade_type'], $payload['type']); | ||
| 201 | + if (!$preserve_notify_url) { | ||
| 202 | + unset($payload['notify_url']); | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + $payload['sign'] = self::generateSign($payload); | ||
| 206 | + | ||
| 207 | + return $payload; | ||
| 208 | + } | ||
| 209 | + | ||
| 210 | + /** | ||
| 211 | + * Generate wechat sign. | ||
| 212 | + * | ||
| 213 | + * @author yansongda <me@yansongda.cn> | ||
| 214 | + * | ||
| 215 | + * @param array $data | ||
| 216 | + * | ||
| 217 | + * @throws InvalidArgumentException | ||
| 218 | + */ | ||
| 219 | + public static function generateSign($data): string | ||
| 220 | + { | ||
| 221 | + $key = self::$instance->key; | ||
| 222 | + | ||
| 223 | + if (is_null($key)) { | ||
| 224 | + throw new InvalidArgumentException('Missing Wechat Config -- [key]'); | ||
| 225 | + } | ||
| 226 | + | ||
| 227 | + ksort($data); | ||
| 228 | + | ||
| 229 | + $string = md5(self::getSignContent($data).'&key='.$key); | ||
| 230 | + | ||
| 231 | + Log::debug('Wechat Generate Sign Before UPPER', [$data, $string]); | ||
| 232 | + | ||
| 233 | + return strtoupper($string); | ||
| 234 | + } | ||
| 235 | + | ||
| 236 | + /** | ||
| 237 | + * Generate sign content. | ||
| 238 | + * | ||
| 239 | + * @author yansongda <me@yansongda.cn> | ||
| 240 | + * | ||
| 241 | + * @param array $data | ||
| 242 | + */ | ||
| 243 | + public static function getSignContent($data): string | ||
| 244 | + { | ||
| 245 | + $buff = ''; | ||
| 246 | + | ||
| 247 | + foreach ($data as $k => $v) { | ||
| 248 | + $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : ''; | ||
| 249 | + } | ||
| 250 | + | ||
| 251 | + Log::debug('Wechat Generate Sign Content Before Trim', [$data, $buff]); | ||
| 252 | + | ||
| 253 | + return trim($buff, '&'); | ||
| 254 | + } | ||
| 255 | + | ||
| 256 | + /** | ||
| 257 | + * Decrypt refund contents. | ||
| 258 | + * | ||
| 259 | + * @author yansongda <me@yansongda.cn> | ||
| 260 | + * | ||
| 261 | + * @param string $contents | ||
| 262 | + */ | ||
| 263 | + public static function decryptRefundContents($contents): string | ||
| 264 | + { | ||
| 265 | + return openssl_decrypt( | ||
| 266 | + base64_decode($contents), | ||
| 267 | + 'AES-256-ECB', | ||
| 268 | + md5(self::$instance->key), | ||
| 269 | + OPENSSL_RAW_DATA | ||
| 270 | + ); | ||
| 271 | + } | ||
| 272 | + | ||
| 273 | + /** | ||
| 274 | + * Convert array to xml. | ||
| 275 | + * | ||
| 276 | + * @author yansongda <me@yansongda.cn> | ||
| 277 | + * | ||
| 278 | + * @param array $data | ||
| 279 | + * | ||
| 280 | + * @throws InvalidArgumentException | ||
| 281 | + */ | ||
| 282 | + public static function toXml($data): string | ||
| 283 | + { | ||
| 284 | + if (!is_array($data) || count($data) <= 0) { | ||
| 285 | + throw new InvalidArgumentException('Convert To Xml Error! Invalid Array!'); | ||
| 286 | + } | ||
| 287 | + | ||
| 288 | + $xml = '<xml>'; | ||
| 289 | + foreach ($data as $key => $val) { | ||
| 290 | + $xml .= is_numeric($val) ? '<'.$key.'>'.$val.'</'.$key.'>' : | ||
| 291 | + '<'.$key.'><![CDATA['.$val.']]></'.$key.'>'; | ||
| 292 | + } | ||
| 293 | + $xml .= '</xml>'; | ||
| 294 | + | ||
| 295 | + return $xml; | ||
| 296 | + } | ||
| 297 | + | ||
| 298 | + /** | ||
| 299 | + * Convert xml to array. | ||
| 300 | + * | ||
| 301 | + * @author yansongda <me@yansongda.cn> | ||
| 302 | + * | ||
| 303 | + * @param string $xml | ||
| 304 | + * | ||
| 305 | + * @throws InvalidArgumentException | ||
| 306 | + */ | ||
| 307 | + public static function fromXml($xml): array | ||
| 308 | + { | ||
| 309 | + if (!$xml) { | ||
| 310 | + throw new InvalidArgumentException('Convert To Array Error! Invalid Xml!'); | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | + libxml_disable_entity_loader(true); | ||
| 314 | + | ||
| 315 | + return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true); | ||
| 316 | + } | ||
| 317 | + | ||
| 318 | + /** | ||
| 319 | + * Get service config. | ||
| 320 | + * | ||
| 321 | + * @author yansongda <me@yansongda.cn> | ||
| 322 | + * | ||
| 323 | + * @param string|null $key | ||
| 324 | + * @param mixed|null $default | ||
| 325 | + * | ||
| 326 | + * @return mixed|null | ||
| 327 | + */ | ||
| 328 | + public function getConfig($key = null, $default = null) | ||
| 329 | + { | ||
| 330 | + if (is_null($key)) { | ||
| 331 | + return $this->config->all(); | ||
| 332 | + } | ||
| 333 | + | ||
| 334 | + if ($this->config->has($key)) { | ||
| 335 | + return $this->config[$key]; | ||
| 336 | + } | ||
| 337 | + | ||
| 338 | + return $default; | ||
| 339 | + } | ||
| 340 | + | ||
| 341 | + /** | ||
| 342 | + * Get app id according to param type. | ||
| 343 | + * | ||
| 344 | + * @author yansongda <me@yansongda.cn> | ||
| 345 | + * | ||
| 346 | + * @param string $type | ||
| 347 | + */ | ||
| 348 | + public static function getTypeName($type = ''): string | ||
| 349 | + { | ||
| 350 | + switch ($type) { | ||
| 351 | + case '': | ||
| 352 | + $type = 'app_id'; | ||
| 353 | + break; | ||
| 354 | + case 'app': | ||
| 355 | + $type = 'appid'; | ||
| 356 | + break; | ||
| 357 | + default: | ||
| 358 | + $type = $type.'_id'; | ||
| 359 | + } | ||
| 360 | + | ||
| 361 | + return $type; | ||
| 362 | + } | ||
| 363 | + | ||
| 364 | + /** | ||
| 365 | + * Get Base Uri. | ||
| 366 | + * | ||
| 367 | + * @author yansongda <me@yansongda.cn> | ||
| 368 | + * | ||
| 369 | + * @return string | ||
| 370 | + */ | ||
| 371 | + public function getBaseUri() | ||
| 372 | + { | ||
| 373 | + return $this->baseUri; | ||
| 374 | + } | ||
| 375 | + | ||
| 376 | + /** | ||
| 377 | + * processingApiResult. | ||
| 378 | + * | ||
| 379 | + * @author yansongda <me@yansongda.cn> | ||
| 380 | + * | ||
| 381 | + * @param $endpoint | ||
| 382 | + * | ||
| 383 | + * @throws GatewayException | ||
| 384 | + * @throws InvalidArgumentException | ||
| 385 | + * @throws InvalidSignException | ||
| 386 | + * | ||
| 387 | + * @return Collection | ||
| 388 | + */ | ||
| 389 | + protected static function processingApiResult($endpoint, array $result) | ||
| 390 | + { | ||
| 391 | + if (!isset($result['return_code']) || 'SUCCESS' != $result['return_code']) { | ||
| 392 | + throw new GatewayException('Get Wechat API Error:'.($result['return_msg'] ?? $result['retmsg'] ?? ''), $result); | ||
| 393 | + } | ||
| 394 | + | ||
| 395 | + if (isset($result['result_code']) && 'SUCCESS' != $result['result_code']) { | ||
| 396 | + throw new BusinessException('Wechat Business Error: '.$result['err_code'].' - '.$result['err_code_des'], $result); | ||
| 397 | + } | ||
| 398 | + | ||
| 399 | + if (false !== strpos($endpoint, 'xdc/apiv2getsignkey') || | ||
| 400 | + false !== strpos($endpoint, 'mmpaymkttransfers') || | ||
| 401 | + self::generateSign($result) === $result['sign']) { | ||
| 402 | + return new Collection($result); | ||
| 403 | + } | ||
| 404 | + | ||
| 405 | + Events::dispatch(new Events\SignFailed('Wechat', '', $result)); | ||
| 406 | + | ||
| 407 | + throw new InvalidSignException('Wechat Sign Verify FAILED', $result); | ||
| 408 | + } | ||
| 409 | + | ||
| 410 | + /** | ||
| 411 | + * setDevKey. | ||
| 412 | + * | ||
| 413 | + * @author yansongda <me@yansongda.cn> | ||
| 414 | + * | ||
| 415 | + * @throws GatewayException | ||
| 416 | + * @throws InvalidArgumentException | ||
| 417 | + * @throws InvalidSignException | ||
| 418 | + * @throws Exception | ||
| 419 | + * | ||
| 420 | + * @return Support | ||
| 421 | + */ | ||
| 422 | + private static function setDevKey() | ||
| 423 | + { | ||
| 424 | + if (Wechat::MODE_DEV == self::$instance->mode) { | ||
| 425 | + $data = [ | ||
| 426 | + 'mch_id' => self::$instance->mch_id, | ||
| 427 | + 'nonce_str' => Str::random(), | ||
| 428 | + ]; | ||
| 429 | + $data['sign'] = self::generateSign($data); | ||
| 430 | + | ||
| 431 | + $result = self::requestApi('https://api.mch.weixin.qq.com/xdc/apiv2getsignkey/sign/getsignkey', $data); | ||
| 432 | + | ||
| 433 | + self::$instance->config->set('key', $result['sandbox_signkey']); | ||
| 434 | + } | ||
| 435 | + | ||
| 436 | + return self::$instance; | ||
| 437 | + } | ||
| 438 | + | ||
| 439 | + /** | ||
| 440 | + * Set Http options. | ||
| 441 | + * | ||
| 442 | + * @author yansongda <me@yansongda.cn> | ||
| 443 | + */ | ||
| 444 | + private function setHttpOptions(): self | ||
| 445 | + { | ||
| 446 | + if ($this->config->has('http') && is_array($this->config->get('http'))) { | ||
| 447 | + $this->config->forget('http.base_uri'); | ||
| 448 | + $this->httpOptions = $this->config->get('http'); | ||
| 449 | + } | ||
| 450 | + | ||
| 451 | + return $this; | ||
| 452 | + } | ||
| 453 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Wechat; | ||
| 4 | + | ||
| 5 | +use Symfony\Component\HttpFoundation\Request; | ||
| 6 | +use Yansongda\Pay\Events; | ||
| 7 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 9 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 10 | +use Yansongda\Pay\Gateways\Wechat; | ||
| 11 | +use Yansongda\Supports\Collection; | ||
| 12 | + | ||
| 13 | +class TransferGateway extends Gateway | ||
| 14 | +{ | ||
| 15 | + /** | ||
| 16 | + * Pay an order. | ||
| 17 | + * | ||
| 18 | + * @author yansongda <me@yansongda.cn> | ||
| 19 | + * | ||
| 20 | + * @param string $endpoint | ||
| 21 | + * | ||
| 22 | + * @throws GatewayException | ||
| 23 | + * @throws InvalidArgumentException | ||
| 24 | + * @throws InvalidSignException | ||
| 25 | + */ | ||
| 26 | + public function pay($endpoint, array $payload): Collection | ||
| 27 | + { | ||
| 28 | + if (Wechat::MODE_SERVICE === $this->mode) { | ||
| 29 | + unset($payload['sub_mch_id'], $payload['sub_appid']); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + $type = Support::getTypeName($payload['type'] ?? ''); | ||
| 33 | + | ||
| 34 | + $payload['mch_appid'] = Support::getInstance()->getConfig($type, ''); | ||
| 35 | + $payload['mchid'] = $payload['mch_id']; | ||
| 36 | + | ||
| 37 | + if ('cli' !== php_sapi_name() && !isset($payload['spbill_create_ip'])) { | ||
| 38 | + $payload['spbill_create_ip'] = Request::createFromGlobals()->server->get('SERVER_ADDR'); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + unset($payload['appid'], $payload['mch_id'], $payload['trade_type'], | ||
| 42 | + $payload['notify_url'], $payload['type']); | ||
| 43 | + | ||
| 44 | + $payload['sign'] = Support::generateSign($payload); | ||
| 45 | + | ||
| 46 | + Events::dispatch(new Events\PayStarted('Wechat', 'Transfer', $endpoint, $payload)); | ||
| 47 | + | ||
| 48 | + return Support::requestApi( | ||
| 49 | + 'mmpaymkttransfers/promotion/transfers', | ||
| 50 | + $payload, | ||
| 51 | + true | ||
| 52 | + ); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + /** | ||
| 56 | + * Find. | ||
| 57 | + * | ||
| 58 | + * @author yansongda <me@yansongda.cn> | ||
| 59 | + * | ||
| 60 | + * @param $order | ||
| 61 | + */ | ||
| 62 | + public function find($order): array | ||
| 63 | + { | ||
| 64 | + return [ | ||
| 65 | + 'endpoint' => 'mmpaymkttransfers/gettransferinfo', | ||
| 66 | + 'order' => is_array($order) ? $order : ['partner_trade_no' => $order], | ||
| 67 | + 'cert' => true, | ||
| 68 | + ]; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + /** | ||
| 72 | + * Get trade type config. | ||
| 73 | + * | ||
| 74 | + * @author yansongda <me@yansongda.cn> | ||
| 75 | + */ | ||
| 76 | + protected function getTradeType(): string | ||
| 77 | + { | ||
| 78 | + return ''; | ||
| 79 | + } | ||
| 80 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Wechat; | ||
| 4 | + | ||
| 5 | +use Symfony\Component\HttpFoundation\RedirectResponse; | ||
| 6 | +use Yansongda\Pay\Events; | ||
| 7 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 8 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 9 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 10 | + | ||
| 11 | +class WapGateway extends Gateway | ||
| 12 | +{ | ||
| 13 | + /** | ||
| 14 | + * Pay an order. | ||
| 15 | + * | ||
| 16 | + * @author yansongda <me@yansongda.cn> | ||
| 17 | + * | ||
| 18 | + * @param string $endpoint | ||
| 19 | + * | ||
| 20 | + * @throws GatewayException | ||
| 21 | + * @throws InvalidArgumentException | ||
| 22 | + * @throws InvalidSignException | ||
| 23 | + */ | ||
| 24 | + public function pay($endpoint, array $payload): RedirectResponse | ||
| 25 | + { | ||
| 26 | + $payload['trade_type'] = $this->getTradeType(); | ||
| 27 | + | ||
| 28 | + Events::dispatch(new Events\PayStarted('Wechat', 'Wap', $endpoint, $payload)); | ||
| 29 | + | ||
| 30 | + $mweb_url = $this->preOrder($payload)->get('mweb_url'); | ||
| 31 | + | ||
| 32 | + $url = is_null(Support::getInstance()->return_url) ? $mweb_url : $mweb_url. | ||
| 33 | + '&redirect_url='.urlencode(Support::getInstance()->return_url); | ||
| 34 | + | ||
| 35 | + return new RedirectResponse($url); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + /** | ||
| 39 | + * Get trade type config. | ||
| 40 | + * | ||
| 41 | + * @author yansongda <me@yansongda.cn> | ||
| 42 | + */ | ||
| 43 | + protected function getTradeType(): string | ||
| 44 | + { | ||
| 45 | + return 'MWEB'; | ||
| 46 | + } | ||
| 47 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Yansongda\Pay\Gateways\Wechat; | ||
| 4 | + | ||
| 5 | +use Symfony\Component\HttpFoundation\RedirectResponse; | ||
| 6 | +use Symfony\Component\HttpFoundation\Response; | ||
| 7 | +use Symfony\Component\HttpFoundation\Request; | ||
| 8 | +use Yansongda\Pay\Events; | ||
| 9 | +use Yansongda\Pay\Exceptions\GatewayException; | ||
| 10 | +use Yansongda\Pay\Exceptions\InvalidArgumentException; | ||
| 11 | +use Yansongda\Pay\Exceptions\InvalidSignException; | ||
| 12 | +use Yansongda\Supports\Collection; | ||
| 13 | + | ||
| 14 | +class WebGateway extends Gateway | ||
| 15 | +{ | ||
| 16 | + /** | ||
| 17 | + * Pay an order. | ||
| 18 | + * | ||
| 19 | + * @param string $endpoint | ||
| 20 | + * @param array $payload | ||
| 21 | + * | ||
| 22 | + * @author yansongda <me@yansongda.cn> | ||
| 23 | + * | ||
| 24 | + */ | ||
| 25 | + public function pay($endpoint, array $payload): Response | ||
| 26 | + { | ||
| 27 | + $payload['spbill_create_ip'] = Request::createFromGlobals()->server->get('SERVER_ADDR'); | ||
| 28 | + $payload['trade_type'] = $this->getTradeType(); | ||
| 29 | + | ||
| 30 | + $code_url = $this->preOrder($payload)['code_url']; | ||
| 31 | + $params = [ | ||
| 32 | + 'body' => $payload['body'], | ||
| 33 | + 'code_url' => $code_url, | ||
| 34 | + 'out_trade_no' => $payload['out_trade_no'], | ||
| 35 | + 'return_url' => Support::getInstance()->return_url, | ||
| 36 | + 'total_fee' => $payload['total_fee'], | ||
| 37 | + ]; | ||
| 38 | + | ||
| 39 | + $params['sign'] = md5(implode('', $params) . Support::getInstance()->app_id); | ||
| 40 | + $endpoint = addon_url("epay/api/wechat"); | ||
| 41 | + | ||
| 42 | + Events::dispatch(new Events\PayStarted('Wechat', 'Web/Wap', $endpoint, $payload)); | ||
| 43 | + | ||
| 44 | + return $this->buildPayHtml($endpoint, $params); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + /** | ||
| 48 | + * Build Html response. | ||
| 49 | + * | ||
| 50 | + * @param string $endpoint | ||
| 51 | + * @param array $payload | ||
| 52 | + * @param string $method | ||
| 53 | + * | ||
| 54 | + * @return Response | ||
| 55 | + * @author yansongda <me@yansongda.cn> | ||
| 56 | + * | ||
| 57 | + */ | ||
| 58 | + protected function buildPayHtml($endpoint, $payload, $method = 'POST'): Response | ||
| 59 | + { | ||
| 60 | + if (strtoupper($method) === 'GET') { | ||
| 61 | + return RedirectResponse::create($endpoint . '?' . http_build_query($payload)); | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + $sHtml = "<form id='wechat_submit' name='wechat_submit' action='" . $endpoint . "' method='" . $method . "'>"; | ||
| 65 | + foreach ($payload as $key => $val) { | ||
| 66 | + $val = str_replace("'", ''', $val); | ||
| 67 | + $sHtml .= "<input type='hidden' name='" . $key . "' value='" . $val . "'/>"; | ||
| 68 | + } | ||
| 69 | + $sHtml .= "<input type='submit' value='ok' style='display:none;'></form>"; | ||
| 70 | + $sHtml .= "<script>document.forms['wechat_submit'].submit();</script>"; | ||
| 71 | + | ||
| 72 | + return Response::create($sHtml); | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * Get trade type config. | ||
| 77 | + * | ||
| 78 | + * @return string | ||
| 79 | + * @author yansongda <me@yansongda.cn> | ||
| 80 | + * | ||
| 81 | + */ | ||
| 82 | + protected function getTradeType(): string | ||
| 83 | + { | ||
| 84 | + return 'NATIVE'; | ||
| 85 | + } | ||
| 86 | +} |
-
请 注册 或 登录 后发表评论