├── .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 |
--------------------------------------------------------------------------------