云上君阙 發表於 2020-9-3 14:51:00

php对接“paypal/Checkout-PHP-SDK“支付流程

<h2 data-id="heading-1">前言</h2>
<hr>
<p>&nbsp;公司一个网站项目有国外的用户给我们发邮件希望能用paypal支付,于是交给了我,我们这个项目两年前是有对接paypal通道的,但是一直没有开放,测试测了并不能完成付款流程。</p>
<p>看paypal官方是最近有出一个新的sdk,老的应该不在支持更新了,于是打算用新的SDK重新对接,新的github地址:https://github.com/paypal/Checkout-PHP-SDK/。</p>
<p>这个项目使用的是thinkphp5框架,但是其他框架使用方法也都差不多。</p>
<p>&nbsp;</p>
<p>本文只是随笔记录,如果能帮到其他朋友最好,如果有哪里错误或者有疑问的可以在评论区指出,我看到会及时回复的!!!</p>
<h2 data-id="heading-1">准备</h2>
<hr>
<p>&nbsp;</p>
<p>&nbsp;测试账户登录https://developer.paypal.com开发者中心</p>
<p><img src="https://img2020.cnblogs.com/blog/1851735/202009/1851735-20200903143132237-1660597356.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;点开并创建新的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>&nbsp;</p>
<p>左侧account可以会有默认的商家账户和普通用户,可以用来沙盒环境测试。</p>
<p><strong>遇到的坑</strong></p>
<p>沙盒环境下用户能扣款,也能收到通知,但是商家账户没有收款记录,发邮件给paypal,说是他们帮我确认下收款邮箱了,然后再试就可以了。</p>
<p>正式环境下国内大陆的paypal账户不能给国内大陆的收款账户付款,但是用visa等信用卡可以选择香港地区进行付款</p>
<h2 data-id="heading-1">对接流程</h2>
<hr>
<p>&nbsp;</p>
<p>composer.json添加“paypal/Checkout-PHP-SDK”之后执行 <code>composer update paypal/Checkout-PHP-SDK&nbsp; 记得要带包名,不然所有的包全都更新到最新版了</code></p>
<p>更新好之后会发现vendor目录多了两个包</p>
<p><img src="https://img2020.cnblogs.com/blog/1851735/202009/1851735-20200903135318283-816422447.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>并且有写好的demo</p>
<p><img src="https://img2020.cnblogs.com/blog/1851735/202009/1851735-20200903135359508-339657956.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;我们目前需要使用的是发起订单,订单扣款,以及订单退款。</p>
<p>&nbsp;</p>
<p><span style="font-size: 14pt"><strong>&nbsp;发起订单</strong></span></p>
<p><span style="font-size: 14pt"><strong>&nbsp;</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>-&gt;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>-&gt;body = <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">(
                        </span>'intent' =&gt; 'CAPTURE',
                        'application_context' =&gt;
                            <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">(
                              </span>'return_url' =&gt; <span style="color: rgba(128, 0, 128, 1)">$returnUrl</span>,
                              'cancel_url' =&gt; <span style="color: rgba(128, 0, 128, 1)">$cancelUrl</span><span style="color: rgba(0, 0, 0, 1)">
                            )</span>,
                        'purchase_units' =&gt;
                            <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">(
                              </span>0 =&gt;
                                    <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">(
                                        </span>'amount' =&gt;
                                          <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">(
                                                </span>'currency_code' =&gt; <span style="color: rgba(128, 0, 128, 1)">'USD'</span>,    //币种-paypal文档有
                                                'value' =&gt; <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>-&gt;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>-&gt;result-&gt;links-&gt;<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-&gt;statusCode;
                        //print_r($ex-&gt;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>&nbsp;</p>
<p><span style="font-size: 14pt"><strong>&nbsp;捕获订单</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.&nbsp; 创建订单<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>-&gt;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>-&gt;result-&gt;<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>-&gt;result-&gt;<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>-&gt;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>-&gt;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>-&gt;result-&gt;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>-&gt;result-&gt;<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>-&gt;<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>&nbsp;</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>-&gt;execute(<span style="color: rgba(128, 0, 128, 1)">$request</span>);    </pre>
</div>
<p>&nbsp;</p>
<p>退款流程应也是根据token来操作,但是我这边提示我“description":"You do not have permission to access or perform operations on this resource.”&nbsp;</p>
<p>https://stackoverflow.com/questions/54907374/paypal-refund-rest-api-v2-authorization-failed-due-to-insufficient-permissions&nbsp;&nbsp; 这里说是需要新建一个app_id才行,因为我们这个项目退款要经过财务,所以就没继续测试下去,有走完流程的朋友可以说一下。</p>
<p>&nbsp;</p>
<p><span style="font-size: 18pt"><strong>付款回调</strong></span></p>
<hr>
<p>&nbsp;</p>
<p>&nbsp;paypal应该是ipn和webhook,但是我看Stack Overflow上有人说ipn方式可能会废弃,所以我们采用了webhook方式</p>
<p>&nbsp;</p>
<p>webhook需要点开对应的APP下去添加</p>
<p><img src="https://img2020.cnblogs.com/blog/1851735/202009/1851735-20200903144709333-87889483.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>这里可以选全部也可以选需要的事件</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/xiaolele1/p/13607517.html
頁: [1]
查看完整版本: php对接“paypal/Checkout-PHP-SDK“支付流程