如何将PHP实现的PayPal结账从GET方式改为安全POST方式
问题解答
一、旧版PayPal结账GET改POST方案(解决参数篡改问题)
你当前使用的是PayPal HTML Payments Standard模式,GET方式所有参数都暴露在URL中,确实极易被篡改。改造为POST提交的逻辑很简单,不再直接返回拼接好的跳转URL,而是输出自动提交的POST表单,所有参数放在隐藏域中传递,避免用户端随意修改。
改造后的checkout方法代码如下:
public function checkout() { $query = []; $query['cmd'] = '_cart'; $query['upload'] = 1; $query['business'] = $this->getCredential(); foreach ($this->getItems() as $id => $item) { $id = $id + 1; $query['item_name_' . $id] = $item['name']; $query['amount_' . $id] = $item['amount']; $query['quantity_' . $id] = $item['quantity']; } $query['custom'] = $this->getReference(); $query['first_name'] = $this->first_name; $query['last_name'] = $this->last_name; $query['email'] = $this->customer_email; $query['notify_url'] = $this->getNotificationURL(); $query['return'] = $this->getReturnURL(); $query['cancel_return'] = $this->getCancelURL(); $query['rm'] = '2'; $query['cbt'] = 'Retornar para o site'; $query['lc'] = $this->getLocation(); $query['currency_code'] = $this->getCurrency(); $paypalUrl = "https://". ($this->isSandbox() ? 'sandbox' : 'www' ) .".paypal.com/cgi-bin/webscr"; // 生成自动提交的POST表单 $form = '<form id="paypal_form" action="'.$paypalUrl.'" method="post">'; foreach ($query as $key => $value) { $form .= '<input type="hidden" name="'.htmlspecialchars($key).'" value="'.htmlspecialchars($value).'">'; } $form .= '</form><script>document.getElementById("paypal_form").submit();</script>'; return $form; }
安全补充提示:就算改为POST提交,也必须在IPN回调中校验返回的business字段和你的官方收款邮箱一致、校验mc_gross金额和订单应付金额一致、校验payment_status为Completed,才能判定支付有效,彻底避免参数篡改风险。
二、PayPal v2 Checkout API跳转后未完成支付的解决方案
这个问题的核心原因是你只完成了「创建订单」的步骤,用户在PayPal页面同意支付后跳回你的return_url,仅代表用户完成了支付授权,你还没有调用资金捕获接口完成扣款,资金还在用户账户中,自然不算支付完成。
解决步骤如下:
- 新增return_url对应的后端处理逻辑,从跳转参数中获取
token字段(即订单ID) - 调用PayPal v2 订单捕获接口完成扣款,校验订单状态为
COMPLETED后再更新你的订单状态、发放用户权益 - 同时在notify_url对应的webhook逻辑中做相同的校验,避免用户关闭页面未跳转回return_url导致的订单不同步问题
捕获订单的参考代码如下:
public function handlePaypalReturn() { $orderId = $_GET['token']; $accessToken = $this->getAccessToken(); $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, "https://api-m.paypal.com/v2/checkout/orders/{$orderId}/capture"); curl_setopt($curl, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Authorization: ' . $accessToken, 'Prefer: return=representation' ]); curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode((object)[])); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); $response = json_decode(curl_exec($curl)); $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); if ($httpCode == 201 && $response->status == 'COMPLETED') { // 支付成功,比对订单金额、商品信息,更新你的订单状态、发放权益 } else { // 支付失败,跳转至错误提示页 } }
补充优化提示:原来创建订单的逻辑建议增加错误判断,先校验curl返回的HTTP状态码为201再取跳转链接,不要硬编码取links[1],避免接口结构变化导致报错。
内容的提问来源于stack exchange,提问作者Luís Henrique
相关产品推荐
相关产品推荐

