php对接“paypal/Checkout-PHP-SDK“支付流程
<h2 data-id="heading-1">前言</h2><hr>
<p> 公司一个网站项目有国外的用户给我们发邮件希望能用paypal支付,于是交给了我,我们这个项目两年前是有对接paypal通道的,但是一直没有开放,测试测了并不能完成付款流程。</p>
<p>看paypal官方是最近有出一个新的sdk,老的应该不在支持更新了,于是打算用新的SDK重新对接,新的github地址:https://github.com/paypal/Checkout-PHP-SDK/。</p>
<p>这个项目使用的是thinkphp5框架,但是其他框架使用方法也都差不多。</p>
<p> </p>
<p>本文只是随笔记录,如果能帮到其他朋友最好,如果有哪里错误或者有疑问的可以在评论区指出,我看到会及时回复的!!!</p>
<h2 data-id="heading-1">准备</h2>
<hr>
<p> </p>
<p> 测试账户登录https://developer.paypal.com开发者中心</p>
<p><img src="https://img2020.cnblogs.com/blog/1851735/202009/1851735-20200903143132237-1660597356.png" alt="" loading="lazy"></p>
<p> </p>
<p> 点开并创建新的App,注意这里分sanbox沙盒环境和live环境,沙盒环境可以创建测试付款和收款账户,live是正式环境的。</p>
<p><img src="https://img2020.cnblogs.com/blog/1851735/202009/1851735-20200903143232435-811630520.png" width="900" loading="lazy"></p>
<p> </p>
<p>左侧account可以会有默认的商家账户和普通用户,可以用来沙盒环境测试。</p>
<p><strong>遇到的坑</strong></p>
<p>沙盒环境下用户能扣款,也能收到通知,但是商家账户没有收款记录,发邮件给paypal,说是他们帮我确认下收款邮箱了,然后再试就可以了。</p>
<p>正式环境下国内大陆的paypal账户不能给国内大陆的收款账户付款,但是用visa等信用卡可以选择香港地区进行付款</p>
<h2 data-id="heading-1">对接流程</h2>
<hr>
<p> </p>
<p>composer.json添加“paypal/Checkout-PHP-SDK”之后执行 <code>composer update paypal/Checkout-PHP-SDK 记得要带包名,不然所有的包全都更新到最新版了</code></p>
<p>更新好之后会发现vendor目录多了两个包</p>
<p><img src="https://img2020.cnblogs.com/blog/1851735/202009/1851735-20200903135318283-816422447.png" alt="" loading="lazy"></p>
<p> </p>
<p>并且有写好的demo</p>
<p><img src="https://img2020.cnblogs.com/blog/1851735/202009/1851735-20200903135359508-339657956.png" alt="" loading="lazy"></p>
<p> </p>
<p> </p>
<p> 我们目前需要使用的是发起订单,订单扣款,以及订单退款。</p>
<p> </p>
<p><span style="font-size: 14pt"><strong> 发起订单</strong></span></p>
<p><span style="font-size: 14pt"><strong> </strong></span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">case</span> 'paypal':
<span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(0, 0, 255, 1)">isset</span>(<span style="color: rgba(128, 0, 128, 1)">$Config</span>['app_id']) || !<span style="color: rgba(0, 0, 255, 1)">isset</span>(<span style="color: rgba(128, 0, 128, 1)">$Config</span>['app_secret'<span style="color: rgba(0, 0, 0, 1)">])){</span>
<span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> \think\<span style="color: rgba(0, 0, 255, 1)">Exception</span>('paypal配置错误'<span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(128, 0, 128, 1)">$request</span> = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OrdersCreateRequest();
</span><span style="color: rgba(128, 0, 128, 1)">$request</span>->prefer('return=representation'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$returnUrl</span> = <span style="color: rgba(0, 0, 255, 1)">isset</span>(<span style="color: rgba(128, 0, 128, 1)">$Config</span>['return_url']) ? <span style="color: rgba(128, 0, 128, 1)">$Config</span>['return_url'] : 'https://xxx.com/pay/callback/paypal?act=success'<span style="color: rgba(0, 0, 0, 1)">; //成功跳转地址
</span><span style="color: rgba(128, 0, 128, 1)">$cancelUrl</span> = <span style="color: rgba(0, 0, 255, 1)">isset</span>(<span style="color: rgba(128, 0, 128, 1)">$Config</span>['cancel_url']) ? <span style="color: rgba(128, 0, 128, 1)">$Config</span>['cancel_url'] : 'https://xxx.com/pay/callback/paypal?act=cancel'<span style="color: rgba(0, 0, 0, 1)">; //取消跳转地址
</span><span style="color: rgba(128, 0, 128, 1)">$request</span>->body = <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">(
</span>'intent' => 'CAPTURE',
'application_context' =>
<span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">(
</span>'return_url' => <span style="color: rgba(128, 0, 128, 1)">$returnUrl</span>,
'cancel_url' => <span style="color: rgba(128, 0, 128, 1)">$cancelUrl</span><span style="color: rgba(0, 0, 0, 1)">
)</span>,
'purchase_units' =>
<span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">(
</span>0 =>
<span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">(
</span>'amount' =>
<span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">(
</span>'currency_code' => <span style="color: rgba(128, 0, 128, 1)">'USD'</span>, //币种-paypal文档有
'value' => <span style="color: rgba(128, 0, 128, 1)">'0.01' //金额</span><span style="color: rgba(0, 0, 0, 1)">
)
)
)
);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 是否是沙盒模式</span>
<span style="color: rgba(128, 0, 128, 1)">$envSet</span> = <span style="color: rgba(128, 0, 128, 1)">$Config</span>['use_sandbox'] ? 'PayPalCheckoutSdk\Core\SandboxEnvironment' : 'PayPalCheckoutSdk\Core\ProductionEnvironment'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(128, 0, 128, 1)">$environment</span> = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(128, 0, 128, 1)">$envSet</span>(<span style="color: rgba(128, 0, 128, 1)">$Config</span>['app_id'], <span style="color: rgba(128, 0, 128, 1)">$Config</span>['app_secret'<span style="color: rgba(0, 0, 0, 1)">]);
</span><span style="color: rgba(128, 0, 128, 1)">$client</span> = <span style="color: rgba(0, 0, 255, 1)">new</span> PayPalHttpClient(<span style="color: rgba(128, 0, 128, 1)">$environment</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$response</span> = <span style="color: rgba(128, 0, 128, 1)">$client</span>->execute(<span style="color: rgba(128, 0, 128, 1)">$request</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$url</span> = <span style="color: rgba(128, 0, 128, 1)">$response</span>->result->links-><span style="color: rgba(0, 0, 0, 1)">href;
</span><span style="color: rgba(128, 0, 128, 1)">return $url</span><span style="color: rgba(0, 0, 0, 1)">
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span> (HttpException <span style="color: rgba(128, 0, 128, 1)">$ex</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(128, 0, 128, 1)">$ex</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">echo $ex->statusCode;
//print_r($ex->getMessage());</span>
<span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 0, 255, 1)">break</span>;</pre>
</div>
<p>这里$Config变量是配置参数</p>
<p>这里return的url是需要前端跳转的paypal收银台,用户确认支付后会跳转(这里是同步跳转,异步回调需要ipn或者webhook,后面会提到)</p>
<p> </p>
<p><span style="font-size: 14pt"><strong> 捕获订单</strong></span></p>
<p>用户支付完成跳转到我们这里并没有完成付款,只是给了授权而已,我们还要根据订单的状态来进行扣款操作。</p>
<p>跳转回来或者异步回调会有“token“参数,可以根据token查询当前的订单状态。</p>
<p>订单状态:</p>
<ul>
<li><code class="dax-def-enum-item">CREATED</code>. <span class="dax-def-enum-item-description">The order was created with the specified context. 创建订单<br></span></li>
<li><code class="dax-def-enum-item">SAVED</code>. <span class="dax-def-enum-item-description">The order was saved and persisted. The order status continues to be in progress until a capture is made with <code>final_capture = true</code> for all purchase units within the order.</span></li>
<li><code class="dax-def-enum-item">APPROVED</code>. <span class="dax-def-enum-item-description">The customer approved the payment through the PayPal wallet or another form of guest or unbranded payment. For example, a card, bank account, or so on. 已授权</span></li>
<li><code class="dax-def-enum-item">VOIDED</code>. <span class="dax-def-enum-item-description">All purchase units in the order are voided.</span></li>
<li><code class="dax-def-enum-item">COMPLETED</code>. <span class="dax-def-enum-item-description"><span class="dax-def-enum-item-description">The payment was authorized or the authorized payment was captured for the order. </span></span>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> capture(<span style="color: rgba(128, 0, 128, 1)">$token</span><span style="color: rgba(0, 0, 0, 1)">){
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">1获取token 2 OrdersGetRequest判断token状态status3.COMPLETED 则已完成 APPROVED 则已经批准 进入 captureOrder 判断返回结果COMPLETED 则已完成 其他则打印log</span>
<span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">client</span>
<span style="color: rgba(128, 0, 128, 1)">$Config</span> = \think\Config::get('paypal'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$envSet</span> = <span style="color: rgba(128, 0, 128, 1)">$Config</span>['use_sandbox'] ? 'PayPalCheckoutSdk\Core\SandboxEnvironment' : 'PayPalCheckoutSdk\Core\ProductionEnvironment'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$environment</span> = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(128, 0, 128, 1)">$envSet</span>(<span style="color: rgba(128, 0, 128, 1)">$Config</span>['app_id'], <span style="color: rgba(128, 0, 128, 1)">$Config</span>['app_secret'<span style="color: rgba(0, 0, 0, 1)">]);
</span><span style="color: rgba(128, 0, 128, 1)">$client</span> = <span style="color: rgba(0, 0, 255, 1)">new</span> PayPalHttpClient(<span style="color: rgba(128, 0, 128, 1)">$environment</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//先</span><span style="color: rgba(0, 128, 0, 1)">判断订单当前状态-防止订单已经是已完成状态,再次扣款</span>
<span style="color: rgba(128, 0, 128, 1)">$request</span> = <span style="color: rgba(0, 0, 255, 1)">new</span> OrdersGetRequest(<span style="color: rgba(128, 0, 128, 1)">$token</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$response</span> = <span style="color: rgba(128, 0, 128, 1)">$client</span>->execute(<span style="color: rgba(128, 0, 128, 1)">$request</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(0, 0, 255, 1)">isset</span>(<span style="color: rgba(128, 0, 128, 1)">$response</span>->result-><span style="color: rgba(0, 0, 0, 1)">status))
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> HttpException(<span style="color: rgba(255, 0, 255, 1)">__CLASS__</span> .'参数未获取到,token:'.<span style="color: rgba(128, 0, 128, 1)">$token</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$status</span> = <span style="color: rgba(128, 0, 128, 1)">$response</span>->result-><span style="color: rgba(0, 0, 0, 1)">status;
</span><span style="color: rgba(0, 0, 255, 1)">switch</span> (<span style="color: rgba(128, 0, 128, 1)">$status</span><span style="color: rgba(0, 0, 0, 1)">){
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'COMPLETED': <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">订单已经完成
//</span>
<span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'APPROVED': <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">获取支付金额</span>
<span style="color: rgba(128, 0, 128, 1)">$captureRequest</span> = <span style="color: rgba(0, 0, 255, 1)">new</span> OrdersCaptureRequest(<span style="color: rgba(128, 0, 128, 1)">$token</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$captureRequest</span>->prefer('return=minimal'); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">简洁参数-获取更多参数用return=representation</span>
<span style="color: rgba(128, 0, 128, 1)">$captureResponse</span> = <span style="color: rgba(128, 0, 128, 1)">$client</span>->execute(<span style="color: rgba(128, 0, 128, 1)">$captureRequest</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">返回状态不是已完成</span>
<span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(128, 0, 128, 1)">$captureResponse</span>->result->status != 'COMPLETED'<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> HttpException(<span style="color: rgba(255, 0, 255, 1)">__CLASS__</span> .'captureOrder 失败,token:'.<span style="color: rgba(128, 0, 128, 1)">$token</span>.' status:'.<span style="color: rgba(128, 0, 128, 1)">$captureResponse</span>->result-><span style="color: rgba(0, 0, 0, 1)">status);
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">default</span>: <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">其他</span>
<span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> HttpException(<span style="color: rgba(255, 0, 255, 1)">__CLASS__</span> .'参数未知,token:'.<span style="color: rgba(128, 0, 128, 1)">$token</span>.' status:'.<span style="color: rgba(128, 0, 128, 1)">$status</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">订单支付完成</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}</span><span style="color: rgba(0, 0, 255, 1)">catch</span> (HttpException <span style="color: rgba(128, 0, 128, 1)">$ex</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">Log</span>::write(<span style="color: rgba(128, 0, 128, 1)">$ex</span>-><span style="color: rgba(0, 0, 0, 1)">getMessage());
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<p> </p>
</li>
</ul>
<p><span style="font-size: 14pt"><strong>订单退款</strong></span></p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(128, 0, 128, 1)">$request</span> = <span style="color: rgba(0, 0, 255, 1)">new</span> CapturesRefundRequest(<span style="color: rgba(128, 0, 128, 1)">$token</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$Config</span> = \think\Config::get('paypal'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$envSet</span> = <span style="color: rgba(128, 0, 128, 1)">$Config</span>['use_sandbox'] ? 'PayPalCheckoutSdk\Core\SandboxEnvironment' : 'PayPalCheckoutSdk\Core\ProductionEnvironment'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$environment</span> = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(128, 0, 128, 1)">$envSet</span>(<span style="color: rgba(128, 0, 128, 1)">$Config</span>['app_id'], <span style="color: rgba(128, 0, 128, 1)">$Config</span>['app_secret'<span style="color: rgba(0, 0, 0, 1)">]);
</span><span style="color: rgba(128, 0, 128, 1)">$client</span> = <span style="color: rgba(0, 0, 255, 1)">new</span> PayPalHttpClient(<span style="color: rgba(128, 0, 128, 1)">$environment</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$response</span> = <span style="color: rgba(128, 0, 128, 1)">$client</span>->execute(<span style="color: rgba(128, 0, 128, 1)">$request</span>); </pre>
</div>
<p> </p>
<p>退款流程应也是根据token来操作,但是我这边提示我“description":"You do not have permission to access or perform operations on this resource.” </p>
<p>https://stackoverflow.com/questions/54907374/paypal-refund-rest-api-v2-authorization-failed-due-to-insufficient-permissions 这里说是需要新建一个app_id才行,因为我们这个项目退款要经过财务,所以就没继续测试下去,有走完流程的朋友可以说一下。</p>
<p> </p>
<p><span style="font-size: 18pt"><strong>付款回调</strong></span></p>
<hr>
<p> </p>
<p> paypal应该是ipn和webhook,但是我看Stack Overflow上有人说ipn方式可能会废弃,所以我们采用了webhook方式</p>
<p> </p>
<p>webhook需要点开对应的APP下去添加</p>
<p><img src="https://img2020.cnblogs.com/blog/1851735/202009/1851735-20200903144709333-87889483.png" alt="" loading="lazy"></p>
<p> </p>
<p>这里可以选全部也可以选需要的事件</p>
<p> </p><br><br>
来源:https://www.cnblogs.com/xiaolele1/p/13607517.html
頁:
[1]