├── .gitignore ├── src ├── Exception │ ├── Alipay.php │ ├── Exception.php │ ├── Wechat.php │ ├── WechatApiError.php │ └── AlipayApiError.php ├── Request │ ├── Wechat │ │ ├── App.php │ │ ├── Native.php │ │ ├── Callback.php │ │ ├── JsApi.php │ │ ├── H5.php │ │ └── BaseRequest.php │ └── Alipay │ │ ├── OrderSettleRelationUnBind.php │ │ ├── OffLineQrCode.php │ │ ├── OrderUnSettleQuery.php │ │ ├── OrderSettleRateQuery.php │ │ ├── BalanceQuery.php │ │ ├── TradeClose.php │ │ ├── TradeQuery.php │ │ ├── OrderSettleQuery.php │ │ ├── OrderSettleRelationBind.php │ │ ├── OrderSettleRelationQuery.php │ │ ├── BalanceQueryV3.php │ │ ├── OAuthToken.php │ │ ├── Transfer.php │ │ ├── TransferV3.php │ │ ├── TradeRefund.php │ │ ├── RedPacketPay.php │ │ ├── AccountBillLogQuery.php │ │ ├── OrderSettle.php │ │ ├── BaseRequest.php │ │ ├── PreQrCode.php │ │ ├── App.php │ │ ├── Web.php │ │ ├── Wap.php │ │ └── JsApi.php ├── Response │ ├── Alipay │ │ ├── OffLineQrCode.php │ │ ├── OrderSettleRelationUnBind.php │ │ ├── OrderUnSettleQuery.php │ │ ├── OrderSettleRelationBind.php │ │ ├── TradeClose.php │ │ ├── OrderSettle.php │ │ ├── PreQrCode.php │ │ ├── JsApi.php │ │ ├── OrderSettleRateQuery.php │ │ ├── BalanceQueryV3.php │ │ ├── BalanceQuery.php │ │ ├── Transfer.php │ │ ├── TransferV3.php │ │ ├── OrderSettleQuery.php │ │ ├── SubMerchantSettleConfirm.php │ │ ├── AccountBilLog.php │ │ ├── OAuthToken.php │ │ ├── OrderSettleRelationQuery.php │ │ ├── TradeRefund.php │ │ └── TradeQuery.php │ └── Wechat │ │ ├── H5.php │ │ ├── QueryBean │ │ └── Amount.php │ │ ├── App.php │ │ ├── Native.php │ │ ├── Query.php │ │ └── JsApi.php ├── Beans │ ├── Alipay │ │ ├── AccessParams.php │ │ ├── Gateway.php │ │ ├── SettleExtendParams.php │ │ ├── ExtendParams.php │ │ ├── SubMerchant.php │ │ ├── JsApiExtendParams.php │ │ ├── JsApiBusinessParams.php │ │ ├── SubMerchantSettleInfo.php │ │ ├── PeriodRuleParams.php │ │ ├── ExtUserInfo.php │ │ ├── Participant.php │ │ ├── BusinessParams.php │ │ ├── OpenApiRoyaltyDetailInfoPojo.php │ │ ├── SignParams.php │ │ ├── RoyaltyDetail.php │ │ ├── RoyaltyEntity.php │ │ ├── BaseBean.php │ │ ├── SubMerchantSettleDetailInfo.php │ │ └── GoodsDetail.php │ ├── Wechat │ │ ├── SettleInfo.php │ │ ├── Payer.php │ │ ├── Amount.php │ │ ├── OrderDetail.php │ │ ├── StoreInfo.php │ │ ├── H5Info.php │ │ ├── GoodsDetail.php │ │ ├── H5ReqSceneInfo.php │ │ └── BaseBean.php │ └── Proxy.php ├── Pay.php ├── Utility │ └── AesGcm.php ├── Config │ ├── WechatConfig.php │ └── AlipayConfig.php ├── Wechat.php └── Alipay.php ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /vendor 3 | composer.lock 4 | *.DS_Store 5 | .idea 6 | docker-compose.yml 7 | /tests/test.html 8 | /Temp 9 | -------------------------------------------------------------------------------- /src/Exception/Alipay.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | public array $settle_detail_infos; 15 | 16 | public ?string $settle_period_time; //1d 2d 3d 17 | } -------------------------------------------------------------------------------- /src/Response/Alipay/BalanceQueryV3.php: -------------------------------------------------------------------------------- 1 | payer)){ 15 | $this->payer = new Payer(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Response/Alipay/SubMerchantSettleConfirm.php: -------------------------------------------------------------------------------- 1 | scene_info)){ 15 | $this->scene_info = new H5ReqSceneInfo(); 16 | } 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/Beans/Alipay/ExtUserInfo.php: -------------------------------------------------------------------------------- 1 | h5_info)){ 19 | $this->h5_info = new H5Info(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Response/Alipay/OAuthToken.php: -------------------------------------------------------------------------------- 1 | prepay_id; 17 | } 18 | 19 | /** 20 | * @param mixed $prepay_id 21 | */ 22 | public function setPrepayId($prepay_id): void 23 | { 24 | $this->prepay_id = $prepay_id; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Response/Wechat/Native.php: -------------------------------------------------------------------------------- 1 | code_url; 17 | } 18 | 19 | /** 20 | * @param mixed $code_url 21 | */ 22 | public function setCodeUrl($code_url): void 23 | { 24 | $this->code_url = $code_url; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Beans/Alipay/Participant.php: -------------------------------------------------------------------------------- 1 | start_time = date('Y-m-d H:i:s', $timestamp); 20 | } 21 | 22 | function setEndTime(int $timestamp):void 23 | { 24 | $this->end_time = date('Y-m-d H:i:s', $timestamp); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/Request/Alipay/OrderSettle.php: -------------------------------------------------------------------------------- 1 | amount)){ 40 | $this->amount = new Amount(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Beans/Alipay/BaseBean.php: -------------------------------------------------------------------------------- 1 | $val){ 15 | $temp = new \ReflectionProperty(static::class,$key); 16 | if($temp->getType()->allowsNull()){ 17 | if($val !== null){ 18 | if($val instanceof BaseBean){ 19 | $val = $val->toArray(); 20 | } 21 | $final[$key] = $val; 22 | } 23 | }else{ 24 | if($val === null){ 25 | throw new Alipay(static::class." param {$key} can not be null"); 26 | } 27 | if($val instanceof BaseBean){ 28 | $val = $val->toArray(); 29 | } 30 | $final[$key] = $val; 31 | } 32 | } 33 | return $final; 34 | } 35 | } -------------------------------------------------------------------------------- /src/Beans/Wechat/BaseBean.php: -------------------------------------------------------------------------------- 1 | $val){ 15 | $temp = new \ReflectionProperty(static::class,$key); 16 | if($temp->getType()->allowsNull()){ 17 | if($val !== null){ 18 | if($val instanceof BaseBean){ 19 | $val = $val->toArray(); 20 | } 21 | $final[$key] = $val; 22 | } 23 | }else{ 24 | if($val === null){ 25 | throw new Wechat(static::class." param {$key} can not be null"); 26 | } 27 | if($val instanceof BaseBean){ 28 | $val = $val->toArray(); 29 | } 30 | $final[$key] = $val; 31 | } 32 | } 33 | return $final; 34 | } 35 | } -------------------------------------------------------------------------------- /src/Pay.php: -------------------------------------------------------------------------------- 1 | wechatConfig = $wechat; 31 | return $this; 32 | } 33 | 34 | function setAlipayConfig(AlipayConfig $alipay):static 35 | { 36 | $this->alipayConfig = $alipay; 37 | return $this; 38 | } 39 | 40 | function wechat():Wechat 41 | { 42 | if(!$this->wechat){ 43 | $this->wechat = new Wechat($this->wechatConfig); 44 | } 45 | return $this->wechat; 46 | } 47 | 48 | function alipay() 49 | { 50 | if($this->alipay){ 51 | $this->alipay = new Alipay($this->alipayConfig); 52 | } 53 | return $this->alipay; 54 | } 55 | } -------------------------------------------------------------------------------- /src/Request/Alipay/Wap.php: -------------------------------------------------------------------------------- 1 | package = "prepay_id={$this->prepay_id}"; 31 | $this->nonceStr = strtoupper(Random::character(32)); 32 | $this->timeStamp = time(); 33 | } 34 | 35 | function makeSign(WechatConfig $config):string 36 | { 37 | $body = "{$this->appId}\n{$this->timeStamp}\n{$this->nonceStr}\n{$this->package}\n"; 38 | if (!openssl_sign($body, $signature, $config->getMchPrivateKey(), OPENSSL_ALGO_SHA256)) { 39 | throw new Wechat('Signing the input $message failed, please checking your $privateKey whether or nor correct.'); 40 | } 41 | 42 | $this->paySign = base64_encode($signature); 43 | return $this->paySign; 44 | } 45 | } -------------------------------------------------------------------------------- /src/Request/Alipay/JsApi.php: -------------------------------------------------------------------------------- 1 | goods_id; 26 | } 27 | 28 | public function setGoodsId(string $goods_id): void 29 | { 30 | $this->goods_id = $goods_id; 31 | } 32 | 33 | public function getGoodsName(): string 34 | { 35 | return $this->goods_name; 36 | } 37 | 38 | public function setGoodsName(string $goods_name): void 39 | { 40 | $this->goods_name = $goods_name; 41 | } 42 | 43 | public function getQuantity(): int 44 | { 45 | return $this->quantity; 46 | } 47 | 48 | public function setQuantity(int $quantity): void 49 | { 50 | $this->quantity = $quantity; 51 | } 52 | 53 | public function getPrice(): int 54 | { 55 | return $this->price; 56 | } 57 | 58 | public function setPrice(int $price): void 59 | { 60 | $this->price = $price; 61 | } 62 | 63 | public function getGoodsCategory(): string 64 | { 65 | return $this->goods_category; 66 | } 67 | 68 | public function setGoodsCategory(string $goods_category): void 69 | { 70 | $this->goods_category = $goods_category; 71 | } 72 | 73 | public function getCategoriesTree(): string 74 | { 75 | return $this->categories_tree; 76 | } 77 | 78 | public function setCategoriesTree(string $categories_tree): void 79 | { 80 | $this->categories_tree = $categories_tree; 81 | } 82 | 83 | public function getShowUrl(): string 84 | { 85 | return $this->show_url; 86 | } 87 | 88 | public function setShowUrl(string $show_url): void 89 | { 90 | $this->show_url = $show_url; 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/Utility/AesGcm.php: -------------------------------------------------------------------------------- 1 | static::BLOCK_SIZE || ($tagLength < 12 && $tagLength !== 8 && $tagLength !== 4)) { 50 | throw new Wechat('The inputs `$ciphertext` incomplete, the bytes length must be one of 16, 15, 14, 13, 12, 8 or 4.'); 51 | } 52 | 53 | $plaintext = openssl_decrypt(substr($ciphertext, 0, $tailLength), static::ALGO_AES_256_GCM, $key, OPENSSL_RAW_DATA, $iv, $authTag, $aad); 54 | 55 | if (false === $plaintext) { 56 | throw new Wechat('Decrypting the input $ciphertext failed, please checking your $key and $iv whether or nor correct.'); 57 | } 58 | 59 | return $plaintext; 60 | } 61 | } -------------------------------------------------------------------------------- /src/Config/WechatConfig.php: -------------------------------------------------------------------------------- 1 | mch_private_key; 27 | } 28 | 29 | /** 30 | * @param mixed $mch_private_key 31 | */ 32 | public function setMchPrivateKey($mch_private_key): void 33 | { 34 | $this->mch_private_key = $mch_private_key; 35 | } 36 | 37 | /** 38 | * @return mixed 39 | */ 40 | public function getMchCertSerialNo() 41 | { 42 | return $this->mch_cert_serial_no; 43 | } 44 | 45 | /** 46 | * @param mixed $mch_cert_serial_no 47 | */ 48 | public function setMchCertSerialNo($mch_cert_serial_no): void 49 | { 50 | $this->mch_cert_serial_no = $mch_cert_serial_no; 51 | } 52 | 53 | /** 54 | * @return mixed 55 | */ 56 | public function getMchId() 57 | { 58 | return $this->mch_id; 59 | } 60 | 61 | /** 62 | * @param mixed $mch_id 63 | */ 64 | public function setMchId($mch_id): void 65 | { 66 | $this->mch_id = $mch_id; 67 | } 68 | 69 | /** 70 | * @return mixed 71 | */ 72 | public function getAppId() 73 | { 74 | return $this->app_id; 75 | } 76 | 77 | /** 78 | * @param mixed $app_id 79 | */ 80 | public function setAppId($app_id): void 81 | { 82 | $this->app_id = $app_id; 83 | } 84 | 85 | /** 86 | * @return mixed 87 | */ 88 | public function getMchPublicKey() 89 | { 90 | return $this->mch_public_key; 91 | } 92 | 93 | /** 94 | * @param mixed $mch_public_key 95 | */ 96 | public function setMchPublicKey($mch_public_key): void 97 | { 98 | $this->mch_public_key = $mch_public_key; 99 | } 100 | 101 | /** 102 | * @return mixed 103 | */ 104 | public function getEncryptKey() 105 | { 106 | return $this->encrypt_key; 107 | } 108 | 109 | /** 110 | * @param mixed $encrypt_key 111 | */ 112 | public function setEncryptKey($encrypt_key): void 113 | { 114 | $this->encrypt_key = $encrypt_key; 115 | } 116 | 117 | protected function initialize(): void 118 | { 119 | if(($this->mch_private_key !== null) && is_file($this->mch_private_key)){ 120 | $this->mch_private_key = file_get_contents($this->mch_private_key); 121 | } 122 | 123 | if(($this->mch_public_key !== null) && is_file($this->mch_public_key)){ 124 | $this->mch_public_key = file_get_contents($this->mch_public_key); 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /src/Config/AlipayConfig.php: -------------------------------------------------------------------------------- 1 | appId; 36 | } 37 | 38 | /** 39 | * @param mixed $appId 40 | */ 41 | public function setAppId($appId): void 42 | { 43 | $this->appId = $appId; 44 | } 45 | 46 | /** 47 | * @return mixed 48 | */ 49 | public function getNotifyUrl() 50 | { 51 | return $this->notifyUrl; 52 | } 53 | 54 | /** 55 | * @param mixed $notifyUrl 56 | */ 57 | public function setNotifyUrl($notifyUrl): void 58 | { 59 | $this->notifyUrl = $notifyUrl; 60 | } 61 | 62 | /** 63 | * @return mixed 64 | */ 65 | public function getReturnUrl():?string 66 | { 67 | return $this->returnUrl; 68 | } 69 | 70 | /** 71 | * @param mixed $returnUrl 72 | */ 73 | public function setReturnUrl($returnUrl): void 74 | { 75 | $this->returnUrl = $returnUrl; 76 | } 77 | 78 | /** 79 | * @return mixed 80 | */ 81 | public function getAlipayPublicKey() 82 | { 83 | return $this->alipayPublicKey; 84 | } 85 | 86 | /** 87 | * @param mixed $alipayPublicKey 88 | */ 89 | public function setAlipayPublicKey($alipayPublicKey): void 90 | { 91 | $this->alipayPublicKey = $alipayPublicKey; 92 | } 93 | 94 | /** 95 | * @return mixed 96 | */ 97 | public function getAppPrivateKey() 98 | { 99 | return $this->appPrivateKey; 100 | } 101 | 102 | /** 103 | * @param mixed $appPrivateKey 104 | */ 105 | public function setAppPrivateKey($appPrivateKey): void 106 | { 107 | $this->appPrivateKey = $appPrivateKey; 108 | } 109 | 110 | public function getGateWay(): Gateway 111 | { 112 | return $this->gateWay; 113 | } 114 | 115 | public function setGateWay(Gateway $gateWay): void 116 | { 117 | $this->gateWay = $gateWay; 118 | } 119 | 120 | public function getCharset(): string 121 | { 122 | return $this->charset; 123 | } 124 | 125 | public function getFormat(): string 126 | { 127 | return $this->format; 128 | } 129 | 130 | public function getSignType(): string 131 | { 132 | return $this->signType; 133 | } 134 | 135 | public function getApiVersion(): string 136 | { 137 | return $this->apiVersion; 138 | } 139 | 140 | public function setApiVersion(string $apiVersion): void 141 | { 142 | $this->apiVersion = $apiVersion; 143 | } 144 | 145 | /** 146 | * @return mixed 147 | */ 148 | public function getAppAuthToken() 149 | { 150 | return $this->appAuthToken; 151 | } 152 | 153 | /** 154 | * @param mixed $appAuthToken 155 | */ 156 | public function setAppAuthToken($appAuthToken): void 157 | { 158 | $this->appAuthToken = $appAuthToken; 159 | } 160 | 161 | public function isCertMode(): bool 162 | { 163 | return $this->certMode; 164 | } 165 | 166 | public function setCertMode(bool $certMode): void 167 | { 168 | $this->certMode = $certMode; 169 | } 170 | 171 | public function getAlipayPublicCert(): string 172 | { 173 | return $this->alipayPublicCert; 174 | } 175 | 176 | public function setAlipayPublicCert(string $alipayPublicCert): void 177 | { 178 | $this->alipayPublicCert = $alipayPublicCert; 179 | } 180 | 181 | public function getAlipayRootCert(): ?string 182 | { 183 | return $this->alipayRootCert; 184 | } 185 | 186 | public function setAlipayRootCert(string $alipayRootCert): void 187 | { 188 | $this->alipayRootCert = $alipayRootCert; 189 | } 190 | 191 | public function getAppPublicCert(): string 192 | { 193 | return $this->appPublicCert; 194 | } 195 | 196 | public function setAppPublicCert(string $appPublicCert): void 197 | { 198 | $this->appPublicCert = $appPublicCert; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pay 2 | 3 | ## 安装 4 | 5 | ```bash 6 | composer require easyswoole/pay="2.x" 7 | ``` 8 | 9 | ## 支付宝(v3) 10 | 11 | 支付宝v3版本目前支持 [单笔转账](https://opendocs.alipay.com/open-v3/08e7ef12_alipay.fund.trans.uni.transfer)、[用户授权换取授权令牌](https://opendocs.alipay.com/open-v3/ba2f3ec8_alipay.system.oauth.token?scene=common&pathHash=d166c947)、[支付宝会员授权信息查询](https://opendocs.alipay.com/open-v3/b6702530_alipay.user.info.share?scene=common&pathHash=1be6d230) 12 | 3个接口。 13 | 14 | ### 配置 15 | 16 | > 组件支持支付宝支付两种签名方式(公钥模式、证书模式) 17 | 18 | 公钥模式配置如下: 19 | 20 | ```php 21 | setAppPublicCert('应用公钥证书字符串'); 25 | $alipayConfig->setAppPrivateKey('应用私钥字符串(非JAVA语言)'); 26 | $alipayConfig->setAlipayPublicKey('支付宝公钥字符串'); 27 | $alipayConfig->setAppId('应用appid,如:9021000140620408'); 28 | $alipay = new \EasySwoole\Pay\Alipay($alipayConfig); 29 | ``` 30 | 31 | 证书模式配置如下: 32 | 33 | ```php 34 | setCertMode(true); 38 | $alipayConfig->setAppPrivateKey('应用私钥字符串(非JAVA语言)'); 39 | $alipayConfig->setAppPublicCert('应用公钥证书绝对路径'); 40 | //如:$alipayConfig->setAppPublicCert(file_get_contents(__DIR__ . '/appPublicCert.crt')); 41 | $alipayConfig->setAlipayPublicCert('支付宝公钥证书绝对路径'); 42 | //如:$alipayConfig->setAlipayPublicCert(file_get_contents(__DIR__ . '/alipayPublicCert.crt')); 43 | $alipayConfig->setAlipayRootCert('支付宝根证书绝对路径'); 44 | //如:$alipayConfig->setAlipayRootCert(file_get_contents(__DIR__ . '/alipayRootCert.crt')); 45 | $alipayConfig->setAppId('应用appid,如:9021000140620408'); 46 | $alipay = new \EasySwoole\Pay\Alipay($alipayConfig); 47 | ``` 48 | 49 | ### 单笔转账 50 | 51 | 支付宝官方文档接口地址:https://opendocs.alipay.com/open-v3/08e7ef12_alipay.fund.trans.uni.transfer?scene=ca56bca529e64125a2786703c6192d41&pathHash=d1ccfb8d 52 | 53 | ```php 54 | setAppPublicCert('应用公钥证书字符串'); 58 | $alipayConfig->setAppPrivateKey('应用私钥字符串(非JAVA语言)'); 59 | $alipayConfig->setAlipayPublicKey('支付宝公钥字符串'); 60 | $alipayConfig->setAppId('应用appid,如:9021000140620408'); 61 | $alipay = new \EasySwoole\Pay\Alipay($alipayConfig); 62 | 63 | $request = new \EasySwoole\Pay\Request\Alipay\TransferV3(); 64 | $request->out_biz_no = '201806300001'; 65 | $request->trans_amount = '23.00'; 66 | $request->order_title = '201905代发'; 67 | $request->payee_info = [ 68 | 'cert_type' => 'IDENTITY_CARD', 69 | 'cert_no' => '1201152******72917', 70 | 'identity' => '2088123412341234', 71 | 'name' => '黄龙国际有限公司', 72 | 'identity_type' => 'ALIPAY_USER_ID', 73 | ]; 74 | $request->remark = '201905代发'; 75 | $request->business_params = '{"payer_show_name_use_alias":"true"}'; 76 | 77 | $result = $alipay->transfer($request); 78 | $outBizNo = $result->out_biz_no; //商户订单号 79 | $orderId = $result->order_id; //支付宝转账订单号 80 | $payFundOrderId = $result->pay_fund_order_id;//支付宝支付资金流水号 81 | $transDate = $result->trans_date; //订单支付时间 82 | $status = $result->status;//转账单据状态 83 | ``` 84 | 85 | ### 用户授权换取授权令牌 86 | 87 | 支付宝官方文档接口地址:https://opendocs.alipay.com/open-v3/ba2f3ec8_alipay.system.oauth.token?scene=common&pathHash=d166c947 88 | 89 | ```php 90 | setCertMode(true); 94 | $alipayConfig->setAppPrivateKey('应用私钥字符串(非JAVA语言)'); 95 | $alipayConfig->setAppPublicCert(file_get_contents(__DIR__ . '/appPublicCert.crt')); 96 | $alipayConfig->setAlipayPublicCert(file_get_contents(__DIR__ . '/alipayPublicCert.crt')); 97 | $alipayConfig->setAlipayRootCert(file_get_contents(__DIR__ . '/alipayRootCert.crt')); 98 | $alipayConfig->setAppId('应用appid,如:9021000140620408'); 99 | $alipay = new \EasySwoole\Pay\Alipay($alipayConfig); 100 | 101 | $request = new \EasySwoole\Pay\Request\Alipay\OAuthToken(); 102 | //$request->grant_type = 'authorization_code'; 103 | $request->code = '4b203fe6c11548bcabd8da5bb087a83b'; 104 | $result = $alipay->token($request); 105 | $accessToken = $result->access_token; //访问令牌 106 | $expiresIn = $result->expires_in; //访问令牌的有效时间 107 | $refreshToken = $result->refresh_token; //刷新令牌 108 | $reExpiresIn = $result->re_expires_in; //刷新令牌的有效时间 109 | $userId = $result->user_id; //支付宝用户的唯一标识 110 | $openId = $result->open_id; //支付宝用户唯一标识 111 | $oauthStart = $result->auth_start;//授权token开始时间 112 | ``` 113 | 114 | ### 支付宝会员授权信息查询 115 | 116 | 支付宝官方文档接口地址:https://opendocs.alipay.com/open-v3/b6702530_alipay.user.info.share?scene=common&pathHash=1be6d230 117 | 118 | ```php 119 | setAppPublicCert('应用公钥证书字符串'); 123 | $alipayConfig->setAppPrivateKey('应用私钥字符串(非JAVA语言)'); 124 | $alipayConfig->setAlipayPublicKey('支付宝公钥字符串'); 125 | $alipayConfig->setAppId('应用appid,如:9021000140620408'); 126 | $alipay = new \EasySwoole\Pay\Alipay($alipayConfig); 127 | 128 | $accessToken = '20120823ac6ffaa4d2d84e7384bf983531473993'; 129 | $result = $alipay->userInfo($accessToken); 130 | ``` 131 | 132 | ### 直付通发起支付 133 | ``` 134 | $req = new Wap(); 135 | $req->subject = '测试商品'; 136 | $req->total_amount = 1.0; 137 | $req->out_trade_no = 'xxxxxxxxx'; 138 | $req->settle_info = new SubMerchantSettleInfo(); 139 | $req->settle_info->settle_period_time = '1d'; 140 | 141 | $req->sub_merchant = new SubMerchant(); 142 | $req->sub_merchant->merchant_id = 'SMID'; 143 | 144 | $settleDetailInfo = new SubMerchantSettleDetailInfo(); 145 | $settleDetailInfo->trans_in_type = 'defaultSettle'; 146 | 147 | // $settleDetailInfo->trans_in_type = 'userId'; 148 | // $settleDetailInfo->trans_in = 'USERID'; 149 | //OR 150 | // $settleDetailInfo->trans_in_type = 'loginName'; 151 | // $settleDetailInfo->trans_in = 'xxxxx@qq.com'; 152 | 153 | $settleDetailInfo->amount = 1; 154 | 155 | $req->settle_info->settle_detail_infos = [$settleDetailInfo->toArray()]; 156 | 157 | $url = $pay->wap($req); 158 | ``` 159 | 160 | ### 直付通确认结算 161 | 162 | ```php 163 | $req = new OrderSettle(); 164 | $req->out_request_no = time(); 165 | $req->trade_no = 'xxxxxx'; 166 | 167 | $settleDetailInfo = new SubMerchantSettleDetailInfo(); 168 | 169 | $settleDetailInfo->trans_in_type = 'defaultSettle'; 170 | // $settleDetailInfo->trans_in = 'xxxxx@qq.com'; 171 | 172 | $settleDetailInfo->amount = 1; 173 | $req->settle_info = new SubMerchantSettleInfo(); 174 | $req->settle_info->settle_period_time = '1d'; 175 | $req->settle_info->settle_detail_infos = [$settleDetailInfo->toArray()]; 176 | 177 | $ret = $pay->subMerchantSettleConfirm($req); 178 | // var_dump($ret); 179 | ``` 180 | 181 | ### 直付通分账(服务商抽佣) 182 | 183 | ```php 184 | $req = new OrderSettle(); 185 | $req->out_request_no = time(); 186 | $req->trade_no = 'xxxxxxx'; 187 | 188 | $s = new OpenApiRoyaltyDetailInfoPojo(); 189 | $s->royalty_type = 'transfer'; 190 | $s->trans_in_type = 'loginName'; 191 | $s->trans_in = 'xxxxxxx@qq.com'; 192 | $s->amount = 0.2; 193 | 194 | $req->royalty_parameters = [$s->toArray()]; 195 | $ret = $pay->orderSettle($req); 196 | var_dump($ret); 197 | ``` -------------------------------------------------------------------------------- /src/Wechat.php: -------------------------------------------------------------------------------- 1 | getRequest($path); 33 | $json = json_decode($resp->getBody(),true); 34 | if(isset($json['data'])){ 35 | $final = []; 36 | if($autoDecrypt){ 37 | foreach ($json['data'] as $key => $item){ 38 | $str = $this->decrypt($item['encrypt_certificate']['ciphertext'],$item['encrypt_certificate']['nonce'],$item['encrypt_certificate']['associated_data']); 39 | if($str){ 40 | $json['data'][$key]['certificate'] = $str; 41 | } 42 | } 43 | } 44 | 45 | foreach ($json['data'] as $item){ 46 | $final[$item['serial_no']] = $item; 47 | } 48 | return $final; 49 | } 50 | throw new Exception\Wechat("get certificates error with response ".$resp->getBody()); 51 | } 52 | 53 | 54 | function decrypt(string $ciphertext, string $iv = '', string $aad = '') 55 | { 56 | return AesGcm::decrypt($ciphertext,$this->config->getEncryptKey(),$iv,$aad); 57 | } 58 | 59 | function jsApi(JsApi $request): JsApiResponse 60 | { 61 | $path = "/v3/pay/transactions/jsapi"; 62 | $request->mchid = $this->config->getMchId(); 63 | $request->appid = $this->config->getAppId(); 64 | $json = json_encode($request->toArray()); 65 | $resp = $this->postRequest($path,$json); 66 | $json = json_decode($resp->getBody(),true); 67 | 68 | if(isset($json['prepay_id'])){ 69 | $json['appId'] = $this->config->getAppId(); 70 | $ret = new JsApiResponse($json); 71 | $ret->makeSign($this->config); 72 | return $ret; 73 | } 74 | 75 | $ex = new WechatApiError($json['message']); 76 | $ex->apiCode = $json['code']; 77 | if(isset($json['detail'])){ 78 | $ex->detail = $json['detail']; 79 | } 80 | throw $ex; 81 | } 82 | 83 | function app(App $request):AppResponse 84 | { 85 | $path = "/v3/pay/transactions/app"; 86 | $request->setAppid($this->config->getAppId()); 87 | $request->setMchid($this->config->getMchId()); 88 | $json = json_encode($request->toArray(null,$request::FILTER_NOT_NULL)); 89 | $resp = $this->postRequest($path,$json); 90 | $json = json_decode($resp->getBody(),true); 91 | if(isset($json['prepay_id'])){ 92 | return new AppResponse($json); 93 | } 94 | $ex = new Exception\Wechat("App Pay make order error"); 95 | $ex->setHttpResponse($resp->getBody()); 96 | throw $ex; 97 | } 98 | 99 | function h5(H5 $request):H5Response 100 | { 101 | $path = "/v3/pay/transactions/h5"; 102 | $request->mchid = $this->config->getMchId(); 103 | $request->appid = $this->config->getAppId(); 104 | 105 | $json = json_encode($request->toArray(),JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); 106 | $resp = $this->postRequest($path,$json); 107 | $json = json_decode($resp->getBody(),true); 108 | if(isset($json['h5_url'])){ 109 | return new H5Response($json); 110 | } 111 | 112 | $ex = new WechatApiError($json['message']); 113 | $ex->apiCode = $json['code']; 114 | if(isset($json['detail'])){ 115 | $ex->detail = $json['detail']; 116 | } 117 | throw $ex; 118 | } 119 | 120 | function native(Native $request):NativeResponse 121 | { 122 | $path = "/v3/pay/transactions/native"; 123 | $request->setAppid($this->config->getAppId()); 124 | $request->setMchid($this->config->getMchId()); 125 | $json = json_encode($request->toArray($request::FILTER_NOT_NULL)); 126 | $resp = $this->postRequest($path,$json); 127 | $json = json_decode($resp->getBody(),true); 128 | if(isset($json['code_url'])){ 129 | return new NativeResponse($json); 130 | } 131 | $ex = new Exception\Wechat("Native Pay make order error"); 132 | $ex->setHttpResponse($resp->getBody()); 133 | throw $ex; 134 | } 135 | 136 | function query(string $transaction_id) 137 | { 138 | $path = "/v3/pay/transactions/id/{$transaction_id}?mchid={$this->config->getMchId()}"; 139 | $resp = $this->getRequest($path); 140 | $json = json_decode($resp->getBody(),true); 141 | if(isset($json['amount'])){ 142 | return new Query($json); 143 | } 144 | $ex = new WechatApiError($json['message']); 145 | $ex->apiCode = $json['code']; 146 | if(isset($json['detail'])){ 147 | $ex->detail = $json['detail']; 148 | } 149 | throw $ex; 150 | } 151 | 152 | function queryByOutTradeNo(string $out_trade_no) 153 | { 154 | $path = "/v3/pay/transactions/out-trade-no/{$out_trade_no}?mchid={$this->config->getMchId()}"; 155 | $resp = $this->getRequest($path); 156 | $json = json_decode($resp->getBody(),true); 157 | if(isset($json['amount'])){ 158 | return new Query($json); 159 | } 160 | $ex = new WechatApiError($json['message']); 161 | $ex->apiCode = $json['code']; 162 | if(isset($json['detail'])){ 163 | $ex->detail = $json['detail']; 164 | } 165 | throw $ex; 166 | } 167 | 168 | function close(string $out_trade_no) 169 | { 170 | $path = "/v3/pay/transactions/out-trade-no/{$out_trade_no}/close"; 171 | $resp = $this->postRequest($path,json_encode([ 172 | "mchid"=>$this->config->getMchId() 173 | ])); 174 | 175 | if($resp->getStatusCode() == 204){ 176 | return true; 177 | }else{ 178 | $json = json_decode($resp->getBody(),true); 179 | $ex = new WechatApiError($json['message']); 180 | $ex->apiCode = $json['code']; 181 | if(isset($json['detail'])){ 182 | $ex->detail = $json['detail']; 183 | } 184 | throw $ex; 185 | } 186 | } 187 | 188 | 189 | protected function getRequest(string $path):Response 190 | { 191 | $token = $this->sign("GET",$path,""); 192 | $url = "https://api.mch.weixin.qq.com{$path}"; 193 | $client = new HttpClient($url); 194 | $client->setHeader("Authorization",$token,false); 195 | return $client->get(); 196 | } 197 | 198 | protected function postRequest(string $path,string $json):Response 199 | { 200 | $token = $this->sign("POST",$path,$json); 201 | $url = "https://api.mch.weixin.qq.com{$path}"; 202 | $client = new HttpClient($url); 203 | $client->setHeader("Authorization",$token,false); 204 | return $client->postJson($json); 205 | } 206 | 207 | 208 | private function sign(string $method,string $path,string $body) 209 | { 210 | $time = time(); 211 | $nonce = strtoupper( Random::character(32)); 212 | $body = "{$method}\n{$path}\n{$time}\n{$nonce}\n{$body}\n"; 213 | openssl_sign($body, $raw_sign, $this->config->getMchPrivateKey(), OPENSSL_ALGO_SHA256); 214 | $sign = base64_encode($raw_sign); 215 | $schema = 'WECHATPAY2-SHA256-RSA2048 '; 216 | return $schema.sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', 217 | $this->config->getMchId(), $nonce, $time, $this->config->getMchCertSerialNo(), $sign); 218 | } 219 | 220 | 221 | function verify(Callback $callback,?string $publicKey = null):bool 222 | { 223 | if($publicKey == null){ 224 | $list = $this->certificates(); 225 | if(isset($list[$callback->certSerial])){ 226 | $publicKey = $list[$callback->certSerial]['certificate']; 227 | }else{ 228 | throw new Exception\Wechat("certificates serial {$callback->certSerial} not found}"); 229 | } 230 | } 231 | $body = "{$callback->timestamp}\n{$callback->nonce}\n{$callback->body}\n"; 232 | if (($result = openssl_verify($body, base64_decode($callback->signature), $publicKey, OPENSSL_ALGO_SHA256)) === false) { 233 | throw new Exception\Wechat('Verified the input $message failed, please checking your $publicKey whether or nor correct.'); 234 | } 235 | 236 | return $result === 1; 237 | } 238 | 239 | public static function success(): string 240 | { 241 | return 'SUCCESSOK'; 242 | } 243 | 244 | /** 245 | * 结果返回给微信服务器 246 | * @return string 247 | */ 248 | public static function fail(): string 249 | { 250 | return 'FAILFAIL'; 251 | } 252 | } -------------------------------------------------------------------------------- /src/Alipay.php: -------------------------------------------------------------------------------- 1 | config->getGateWay() == Gateway::PRODUCE){ 54 | $this->gateway = 'https://openapi.alipay.com/gateway.do'; 55 | $this->apiV3GateWay = 'https://openapi.alipay.com'; 56 | }else{ 57 | $this->gateway = 'https://openapi-sandbox.dl.alipaydev.com/gateway.do'; 58 | $this->apiV3GateWay = 'https://openapi-sandbox.dl.alipaydev.com'; 59 | } 60 | } 61 | 62 | 63 | /* 64 | * 当面付,预先生成二维码 65 | */ 66 | function preQrCode(PreQrCode $request): Response\Alipay\PreQrCode 67 | { 68 | $res = $this->requestApi($request,'alipay.trade.precreate'); 69 | return new Response\Alipay\PreQrCode($res); 70 | } 71 | 72 | /** 73 | * 订单码 74 | * @param OffLineQrCode $request 75 | * @return Response\Alipay\OffLineQrCode 76 | * @throws AlipayApiError 77 | * @throws Exception\Alipay 78 | */ 79 | function offLineQrCode(OffLineQrCode $request):Response\Alipay\OffLineQrCode 80 | { 81 | $res = $this->requestApi($request,'alipay.trade.precreate'); 82 | return new Response\Alipay\OffLineQrCode($res); 83 | } 84 | 85 | function wap(Wap $request):string 86 | { 87 | $data = $this->buildRequestData($request,'alipay.trade.wap.pay'); 88 | return $this->gateway.'?'.http_build_query($data); 89 | } 90 | 91 | function web(Web $request):string 92 | { 93 | $data = $this->buildRequestData($request,'alipay.trade.page.pay'); 94 | return $this->gateway.'?'.http_build_query($data); 95 | } 96 | 97 | function jsApi(JsApi $request):string 98 | { 99 | $data = $this->buildRequestData($request,'alipay.trade.create'); 100 | return $this->gateway.'?'.http_build_query($data); 101 | } 102 | 103 | function jsApiWithReq(JsApi $req):Response\Alipay\JsApi 104 | { 105 | $res = $this->requestApi($req,'alipay.trade.create'); 106 | return new Response\Alipay\JsApi($res); 107 | } 108 | 109 | function redPacketPayApp(RedPacketPay $request):array 110 | { 111 | return $this->buildRequestData($request,'alipay.fund.trans.app.pay'); 112 | } 113 | 114 | function appPay(App $request):array 115 | { 116 | return $this->buildRequestData($request,'alipay.trade.app.pay'); 117 | } 118 | 119 | /** 120 | * 单笔转账 121 | * doc link: https://opendocs.alipay.com/open-v3/08e7ef12_alipay.fund.trans.uni.transfer 122 | */ 123 | function transferV3(TransferV3 $request):Response\Alipay\TransferV3 124 | { 125 | $path = '/v3/alipay/fund/trans/uni/transfer'; 126 | $url = $this->apiV3GateWay . $path; 127 | $body = $request->toArray(); 128 | $result = $this->requestV3($url, 'POST', $body); 129 | return new Response\Alipay\TransferV3($result); 130 | } 131 | 132 | /** 133 | * @param OrderSettle $request 134 | * @return Response\Alipay\SubMerchantSettleConfirm 135 | * @throws AlipayApiError 136 | * @throws Exception\Alipay 137 | * 直付通结算 138 | */ 139 | function subMerchantSettleConfirm(OrderSettle $request):Response\Alipay\SubMerchantSettleConfirm 140 | { 141 | $res = $this->requestApi($request,'alipay.trade.settle.confirm'); 142 | return new Response\Alipay\SubMerchantSettleConfirm($res); 143 | } 144 | 145 | function transfer(Transfer $request):Response\Alipay\Transfer 146 | { 147 | $res = $this->requestApi($request,'alipay.fund.trans.uni.transfer'); 148 | return new Response\Alipay\Transfer($res); 149 | } 150 | 151 | function accountBillLogQuery(AccountBillLogQuery $request) 152 | { 153 | $res = $this->requestApi($request,'alipay.data.bill.accountlog.query'); 154 | return new AccountBilLog($res); 155 | } 156 | 157 | function orderSettle(OrderSettle $request):Response\Alipay\OrderSettle 158 | { 159 | $res = $this->requestApi($request,'alipay.trade.order.settle'); 160 | return new Response\Alipay\OrderSettle($res); 161 | } 162 | 163 | function orderSettleRelationBind(OrderSettleRelationBind $request):Response\Alipay\OrderSettleRelationBind 164 | { 165 | $res = $this->requestApi($request,'alipay.trade.royalty.relation.bind'); 166 | return new Response\Alipay\OrderSettleRelationBind($res); 167 | } 168 | 169 | function orderSettleRelationUnBind(OrderSettleRelationUnBind $request):Response\Alipay\OrderSettleRelationUnBind 170 | { 171 | $res = $this->requestApi($request,'alipay.trade.royalty.relation.unbind'); 172 | return new Response\Alipay\OrderSettleRelationUnBind($res); 173 | } 174 | 175 | function orderSettleRelationQuery(OrderSettleRelationQuery $request):Response\Alipay\OrderSettleRelationQuery 176 | { 177 | $res = $this->requestApi($request,'alipay.trade.royalty.relation.batchquery'); 178 | $res = new Response\Alipay\OrderSettleRelationQuery($res); 179 | $temp = $res->receiver_list; 180 | $res->receiver_list = []; 181 | foreach ($temp as $item){ 182 | $res->receiver_list[] = new RoyaltyEntity($item); 183 | } 184 | return $res; 185 | } 186 | 187 | function balanceQuery(BalanceQuery $request):Response\Alipay\BalanceQuery 188 | { 189 | $res = $this->requestApi($request,'alipay.data.bill.balance.query'); 190 | return new Response\Alipay\BalanceQuery($res); 191 | } 192 | 193 | function balanceQueryV3(BalanceQueryV3 $request):Response\Alipay\BalanceQueryV3 194 | { 195 | $path = '/v3/alipay/fund/account/query?'.http_build_query($request->toArray()); 196 | $url = $this->apiV3GateWay . $path; 197 | $res = $this->requestV3($url, 'GET',[]); 198 | return new Response\Alipay\BalanceQueryV3($res); 199 | } 200 | 201 | function orderSettleQuery(OrderSettleQuery $request):Response\Alipay\OrderSettleQuery 202 | { 203 | $res = $this->requestApi($request,'alipay.trade.order.settle.query'); 204 | $res = new Response\Alipay\OrderSettleQuery($res); 205 | $temp = $res->royalty_detail_list; 206 | $res->royalty_detail_list = []; 207 | foreach ($temp as $item){ 208 | $res->royalty_detail_list[] = new RoyaltyDetail($item); 209 | } 210 | return $res; 211 | } 212 | 213 | function orderSettleRateQuery(OrderSettleRateQuery $request):Response\Alipay\OrderSettleRateQuery 214 | { 215 | $res = $this->requestApi($request,'alipay.trade.royalty.rate.query'); 216 | return new Response\Alipay\OrderSettleRateQuery($res); 217 | } 218 | 219 | function orderUnSettleQuery(OrderUnSettleQuery $request):Response\Alipay\OrderUnSettleQuery 220 | { 221 | $res = $this->requestApi($request,'alipay.trade.order.onsettle.query'); 222 | return new Response\Alipay\OrderUnSettleQuery($res); 223 | } 224 | 225 | function tradeQuery(TradeQuery $request):Response\Alipay\TradeQuery 226 | { 227 | $res = $this->requestApi($request,'alipay.trade.query'); 228 | return new Response\Alipay\TradeQuery($res); 229 | } 230 | 231 | function tradeClose(TradeClose $request):Response\Alipay\TradeClose 232 | { 233 | $res = $this->requestApi($request,'alipay.trade.close'); 234 | return new Response\Alipay\TradeClose($res); 235 | } 236 | 237 | 238 | function tradeRefund(TradeRefund $request):Response\Alipay\TradeRefund 239 | { 240 | $res = $this->requestApi($request,'alipay.trade.refund'); 241 | return new Response\Alipay\TradeRefund($res); 242 | } 243 | 244 | /** 245 | * 换取授权访问令牌 246 | */ 247 | function authCode2token(OAuthToken $request): Response\Alipay\OAuthToken 248 | { 249 | $path = '/v3/alipay/system/oauth/token'; 250 | $body = [ 251 | 'grant_type' => $request->grant_type 252 | ]; 253 | if (!empty($request->code)) { 254 | $body['code'] = $request->code; 255 | } 256 | if (!empty($request->refresh_token)) { 257 | $body['refresh_token'] = $request->refresh_token; 258 | } 259 | $url = $this->apiV3GateWay . $path; 260 | $res = $this->requestV3($url, 'POST', $body); 261 | return new Response\Alipay\OAuthToken($res); 262 | } 263 | 264 | function authToken2UserInfo(string $authToken) 265 | { 266 | $path = '/v3/alipay/user/info/share'; 267 | $queryParams = ['auth_token' => $authToken]; 268 | $url = $this->apiV3GateWay . $path . '?' . http_build_query($queryParams); 269 | return $this->requestV3($url,'POST',[]); 270 | } 271 | 272 | function openAuth(string $redirectUrl,string $scope = 'auth_base') 273 | { 274 | return "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id={$this->config->getAppId()}&scope={$scope}&redirect_uri=".urlencode($redirectUrl); 275 | } 276 | 277 | function verifyResponse(array $requestData) 278 | { 279 | if( isset( $requestData['fund_bill_list'] ) ){ 280 | $requestData['fund_bill_list'] = htmlspecialchars_decode( $requestData['fund_bill_list'] ); 281 | } 282 | 283 | return $this->verifySign( $requestData ); 284 | } 285 | 286 | public static function success() : string 287 | { 288 | return 'success'; 289 | } 290 | 291 | 292 | public static function fail() : string 293 | { 294 | return 'failure'; 295 | } 296 | 297 | protected function buildRequestData(BaseBean $request,string $method):array 298 | { 299 | $baseRequest = $this->getBaseParams(); 300 | $baseRequest->method = $method; 301 | /** 302 | * 检查notify url 和 return_url 303 | */ 304 | if(isset($request->notify_url)){ 305 | $baseRequest->notify_url = $request->notify_url; 306 | } 307 | 308 | if(isset($request->return_url)){ 309 | $baseRequest->return_url = $request->return_url; 310 | } 311 | 312 | $baseRequest->biz_content = json_encode($request->toArray()); 313 | $requestData = $baseRequest->toArray(); 314 | $sign = $this->generateSign($requestData); 315 | $requestData['sign'] = $sign; 316 | return $requestData; 317 | } 318 | 319 | protected function requestApi(BaseBean $request,string $method) 320 | { 321 | $requestData = $this->buildRequestData($request,$method); 322 | $client = new HttpClient($this->gateway); 323 | if(!empty($this->proxy)){ 324 | $client->setClientSettings($this->proxy->toArray()); 325 | } 326 | $res = $client->post($requestData); 327 | $response = $res->getBody(); 328 | if(!empty($response)){ 329 | $result = json_decode( mb_convert_encoding( $res->getBody(), 'utf-8', 'gb2312' ), true ); 330 | if(is_array($result)){ 331 | $key = str_replace( '.', '_', $method ).'_response'; 332 | if($result[$key]['code'] == '10000'){ 333 | $v = $this->verifySign($result[$key],true,$result['sign']); 334 | if($v){ 335 | return $result[$key]; 336 | }else{ 337 | throw new Exception\Alipay('verify api response sign error'); 338 | } 339 | }else{ 340 | $ex = new AlipayApiError($result[$key]['sub_msg']); 341 | $ex->apiCode = $result[$key]['code']; 342 | $ex->apiSubCode = $result[$key]['sub_code']; 343 | $ex->apiMsg = $result[$key]['msg']; 344 | throw $ex; 345 | } 346 | }else{ 347 | throw new Exception\Alipay("response from {$this->gateway} is not a json format"); 348 | } 349 | }else{ 350 | throw new Exception\Alipay($res->getErrMsg()); 351 | } 352 | } 353 | 354 | protected function verifySign( array $data, $sync = false, $sign = null ) : bool 355 | { 356 | unset($data['sign_type']); 357 | 358 | $publicKey = null; 359 | 360 | if ($this->config->isCertMode()) { 361 | $publicKey = $this->getPublicKey($this->config->getAlipayPublicCert()); 362 | } else if ($this->config->getAlipayPublicKey()) { 363 | $publicKey = $this->config->getAlipayPublicKey(); 364 | $publicKey = "-----BEGIN PUBLIC KEY-----\n".wordwrap( $publicKey, 64, "\n", true )."\n-----END PUBLIC KEY-----"; 365 | $publicKey = openssl_pkey_get_public( $publicKey ); 366 | } 367 | 368 | $sign = $sign ?? $data['sign']; 369 | 370 | $toVerify = $sync ? mb_convert_encoding( json_encode( $data, JSON_UNESCAPED_UNICODE ), 'gb2312', 'utf-8' ) : $this->getSignContent( $data ); 371 | 372 | return openssl_verify( $toVerify, base64_decode( $sign ), $publicKey, OPENSSL_ALGO_SHA256 ) === 1; 373 | } 374 | 375 | protected function getPublicKey($cert):string 376 | { 377 | $pkey = openssl_pkey_get_public($cert); 378 | $keyData = openssl_pkey_get_details($pkey); 379 | return trim($keyData['key']); 380 | } 381 | 382 | protected function getCertSN($cert):string 383 | { 384 | $ssl = openssl_x509_parse($cert); 385 | return md5($this->array2string(array_reverse($ssl['issuer'])) . $ssl['serialNumber']); 386 | } 387 | 388 | protected function getRootCertSN($cert):?string 389 | { 390 | $array = explode("-----END CERTIFICATE-----", $cert); 391 | $SN = null; 392 | for ($i = 0; $i < count($array) - 1; $i++) { 393 | $ssl[$i] = openssl_x509_parse($array[$i] . "-----END CERTIFICATE-----"); 394 | if (strpos($ssl[$i]['serialNumber'], '0x') === 0) { 395 | $ssl[$i]['serialNumber'] = $this->hex2dec($ssl[$i]['serialNumberHex']); 396 | } 397 | if ($ssl[$i]['signatureTypeLN'] == "sha1WithRSAEncryption" || $ssl[$i]['signatureTypeLN'] == "sha256WithRSAEncryption") { 398 | if ($SN == null) { 399 | $SN = md5($this->array2string(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']); 400 | } else { 401 | 402 | $SN = $SN . "_" . md5($this->array2string(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']); 403 | } 404 | } 405 | } 406 | return $SN; 407 | } 408 | 409 | 410 | /** 411 | * 0x转高精度数字 412 | * @param $hex 413 | * @return int|string 414 | */ 415 | protected function hex2dec($hex) 416 | { 417 | $dec = 0; 418 | $len = strlen($hex); 419 | for ($i = 1; $i <= $len; $i++) { 420 | $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i)))); 421 | } 422 | return $dec; 423 | } 424 | 425 | protected function array2string($array) 426 | { 427 | $string = []; 428 | if ($array && is_array($array)) { 429 | foreach ($array as $key => $value) { 430 | $string[] = $key . '=' . $value; 431 | } 432 | } 433 | return implode(',', $string); 434 | } 435 | 436 | private function getBaseParams() : BaseRequest 437 | { 438 | $sysParams = []; 439 | $sysParams["app_id"] = $this->config->getAppId(); 440 | $sysParams["version"] = $this->config->getApiVersion(); 441 | $sysParams["format"] = $this->config->getFormat(); 442 | $sysParams["sign_type"] = $this->config->getSignType(); 443 | $sysParams["timestamp"] = date( "Y-m-d H:i:s" ); 444 | $sysParams["notify_url"] = $this->config->getNotifyUrl(); 445 | $sysParams["return_url"] = $this->config->getReturnUrl(); 446 | $sysParams["charset"] = $this->config->getCharset(); 447 | $sysParams["app_auth_token"] = $this->config->getAppAuthToken(); 448 | if ($this->config->isCertMode()) { 449 | $sysParams["app_cert_sn"] = $this->getCertSN($this->config->getAppPublicCert()); 450 | $sysParams["alipay_root_cert_sn"] = $this->getRootCertSN($this->config->getAlipayRootCert()); 451 | } 452 | return new BaseRequest($sysParams); 453 | } 454 | 455 | private function generateSign( array $params ) : string 456 | { 457 | $privateKey = $this->config->getAppPrivateKey(); 458 | if( is_null( $privateKey ) ){ 459 | throw new Exception\Alipay( 'Missing Alipay Config -- [private_key]' ); 460 | } 461 | // if(file_exists($privateKey)){ 462 | // $privateKey = openssl_pkey_get_private( $privateKey ); 463 | // } else{ 464 | // $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".wordwrap( $privateKey, 64, "\n", true )."\n-----END RSA PRIVATE KEY-----"; 465 | // } 466 | $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".wordwrap( $privateKey, 64, "\n", true )."\n-----END RSA PRIVATE KEY-----"; 467 | openssl_sign( $this->getSignContent( $params ), $sign, $privateKey, OPENSSL_ALGO_SHA256 ); 468 | $sign = base64_encode( $sign ); 469 | return $sign; 470 | } 471 | 472 | private function getSignContent( array $params , $explod = '&') : string 473 | { 474 | ksort( $params ); 475 | $stringToBeSigned = ""; 476 | $i = 0; 477 | foreach( $params as $k => $v ){ 478 | if( $k == 'sign' ){ 479 | continue; 480 | } 481 | if( !empty($v) && "@" != substr( $v, 0, 1 ) ){ 482 | if( $i == 0 ){ 483 | $stringToBeSigned .= "$k"."="."$v"; 484 | } else{ 485 | $stringToBeSigned .= $explod."$k"."="."$v"; 486 | } 487 | $i ++; 488 | } 489 | } 490 | return $stringToBeSigned; 491 | } 492 | 493 | function setProxy(?Proxy $proxy):Alipay 494 | { 495 | $this->proxy = $proxy; 496 | return $this; 497 | } 498 | 499 | private function buildV3AuthString() 500 | { 501 | if($this->config->isCertMode()){ 502 | $nonce = Random::makeUUIDV4(); 503 | $timestamp = intval(microtime(true)*1000); 504 | $app_cert_sn = $this->getCertSN($this->config->getAppPublicCert()); 505 | return "app_id={$this->config->getAppId()},app_cert_sn={$app_cert_sn},nonce={$nonce},timestamp={$timestamp}"; 506 | }else{ 507 | $nonce = Random::makeUUIDV4(); 508 | $timestamp = intval(microtime(true)*1000); 509 | return "app_id={$this->config->getAppId()},nonce={$nonce},timestamp={$timestamp}"; 510 | } 511 | } 512 | 513 | private function buildV3SignString(string $authString,string $httpMethod,string $requestUrl,array $body):string 514 | { 515 | $str = "{$authString}\n"; 516 | $str .= "{$httpMethod}\n"; 517 | $str .= "{$requestUrl}\n"; 518 | if(!empty($body)){ 519 | $body = json_encode($body); 520 | $str .= "{$body}\n"; 521 | }else{ 522 | $str .= "\n"; 523 | } 524 | 525 | if(!empty($this->config->getAppAuthToken())){ 526 | $str .= "{$this->config->getAppAuthToken()}\n"; 527 | } 528 | 529 | return $str; 530 | } 531 | 532 | function requestV3(string $url,string $method,array $body) 533 | { 534 | $authString = $this->buildV3AuthString(); 535 | $urlInfo = parse_url($url); 536 | $path = $urlInfo['path']; 537 | if(!empty($urlInfo['query'])){ 538 | $path .= '?'.$urlInfo['query']; 539 | } 540 | $signBody = $this->buildV3SignString($authString,$method,$path,$body); 541 | 542 | $privateKey = $this->config->getAppPrivateKey(); 543 | if (is_null( $privateKey)){ 544 | throw new Exception\Alipay( 'Missing Alipay Config -- [private_key]'); 545 | } 546 | 547 | $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".wordwrap( $privateKey, 64, "\n", true )."\n-----END RSA PRIVATE KEY-----"; 548 | openssl_sign($signBody, $sign, $privateKey, OPENSSL_ALGO_SHA256 ); 549 | $sign = base64_encode( $sign ); 550 | $authorization = "ALIPAY-SHA256withRSA {$authString},sign={$sign}"; 551 | 552 | $client = new HttpClient($url); 553 | $headers = [ 554 | 'Authorization' => $authorization, 555 | ]; 556 | 557 | if ($this->config->isCertMode() && $this->config->getAlipayRootCert()) { 558 | $certSn = $this->getRootCertSN($this->config->getAlipayRootCert()); 559 | if (!$certSn) { 560 | throw new Exception\Alipay('Alipay Config -- [alipay root cert] error'); 561 | } 562 | $headers['Alipay-Root-Cert-Sn'] = $certSn; 563 | } 564 | 565 | $client->setHeaders($headers, true, false); 566 | 567 | if($method == "POST"){ 568 | if (!empty($body)) { 569 | $body = json_encode($body); 570 | } else { 571 | $body = null; 572 | } 573 | $res = $client->postJson($body); 574 | }else{ 575 | $res = $client->get(); 576 | } 577 | 578 | 579 | $response = $res->getBody(); 580 | if(!empty($response)){ 581 | $respHeaders = $res->getHeaders(); 582 | $verify = $this->verifyResponseV3($response, $respHeaders, true); 583 | if (!$verify) { 584 | throw new Exception\Alipay("verify api response signature error"); 585 | } 586 | 587 | $json = json_decode($response,true); 588 | if(!is_array($json)){ 589 | throw new Exception\Alipay("response from {$this->apiV3GateWay} is not a json format"); 590 | } 591 | 592 | $statusCode = $res->getStatusCode(); 593 | 594 | if ($statusCode < 200 || $statusCode > 299) { 595 | $ex = new AlipayApiError($json['message']); 596 | $ex->apiCode = $json['code']; 597 | throw $ex; 598 | } 599 | 600 | return $json; 601 | }else{ 602 | throw new Exception\Alipay($res->getErrMsg()); 603 | } 604 | } 605 | 606 | protected function verifyResponseV3($respBody, $headers, bool $isCheckSign): bool 607 | { 608 | $sign = $headers['alipay-signature'] ?? null; 609 | if ($isCheckSign && !$sign) { 610 | return true; 611 | } 612 | 613 | $timestamp = $headers['alipay-timestamp'] ?? null; 614 | $nonce = $headers['alipay-nonce'] ?? null; 615 | 616 | //验签 617 | if ($this->config->isCertMode()) { 618 | //证书模式 619 | if (!$this->config->getAlipayPublicCert()) { 620 | throw new Exception\Alipay('支付宝公钥证书错误。请检查公钥文件格式是否正确'); 621 | } 622 | $res = $this->getPublicKey($this->config->getAlipayPublicCert()); 623 | } else { 624 | //公钥模式 625 | $publicKeyStr = $this->config->getAlipayPublicKey(); 626 | $publicKey = "-----BEGIN PUBLIC KEY-----\n".wordwrap($publicKeyStr, 64, "\n", true )."\n-----END PUBLIC KEY-----"; 627 | $res = openssl_pkey_get_public( $publicKey ); 628 | } 629 | 630 | if (!$res) { 631 | if (!$this->config->getAppPrivateKey()) { 632 | return true; 633 | } 634 | throw new Exception\Alipay('支付宝RSA公钥错误。请检查公钥文件格式是否正确'); 635 | } 636 | 637 | $content = $timestamp . "\n" 638 | . $nonce . "\n" 639 | . (!$respBody ? "" : $respBody) . "\n"; 640 | //调用openssl内置方法验签,返回bool值 641 | return (openssl_verify($content, base64_decode($sign), $res, OPENSSL_ALGO_SHA256) === 1); 642 | } 643 | } 644 | --------------------------------------------------------------------------------