支付逻辑简介

跟微信和支付宝支付不一样,微信和支付宝都是先走统一下单接口,然后返回创建的下单会话id等然后传给前端,贝宝支付是我们先通过API发起个支付请求,然后贝宝给我们个支付链接,前端打开我们的支付链接:例如https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-7K8740615W719670D ,然后该链接会有我们支付订单的部分信息,然后利用沙盒的账号支付确认要支付后,会返回到下单时设置的回调支付那里,如下图:

const accept_url = 'http://fumei.com:8082/service.php?action=paypal_callback&com_id=1210';//返回地址
$redirectUrls = new RedirectUrls();
         $redirectUrls->setReturnUrl(self::accept_url.'&orderId='.$order->orderId.'&success=true')
             ->setCancelUrl(self::accept_url.'&orderId='.$order->orderId.'&success=false');

这里回调会带着订单号和发起是否成功或者失败,如果成功,其实到这里我们并没有完成支付,只有在回调地址里才能完成payment支付,不像微信和支付宝,前端调起真实的支付,支付完毕后就要跳转前端准备的支付成功或失败的页面。

PayPal账号设置

  • 创建账号:
    创建沙盒账号 https://www.paypal.com/c2/webapps/mpp/account-selection
  • 登录沙盒后台
    https://developer.paypal.com/developer/applications/
  • 创建项目APP 查看 APPID和Secret
  • PayPal 对接指南 java paypal付款链接_国外支付


  • PayPal 对接指南 java paypal付款链接_php_02

  • 设置沙盒支付测试账号,充值,创建
  • PayPal 对接指南 java paypal付款链接_php_03

  • 设置回调地址
  • PayPal 对接指南 java paypal付款链接_支付_04

  • 代码实现
  • SDK下载
  • 引入composer 管理包或者直接下载
  • 引入文件
use PayPal\Api\Payer;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\Details;
use PayPal\Api\Amount;
use PayPal\Api\Transaction;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Payment;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Exception\PayPalConnectionException;
use PayPal\Rest\ApiContext;
use PayPal\Api\PaymentExecution;
  • 初始化
protected $PayPal;

   public function __construct()
   {
       $this->PayPal = new ApiContext(
           new OAuthTokenCredential(
               self::clientId,
               self::clientSecret
           )
       );
       $this->PayPal->setConfig(
           array(
               'mode' => 'sandbox',//线上的则配置为live
               'log.LogEnabled' => true,
               'log.FileName' => '../PayPal.log',
               'log.LogLevel' => 'DEBUG', // PLEASE USE `INFO` LEVEL FOR LOGGING IN LIVE ENVIRONMENTS
               'cache.enabled' => true
           )
       );
   }
  • 创建支付链接(即下单接口)
/**
    * @param
    * $product 商品
    * $price 价钱
    * $shipping 运费
    * $description 描述内容
    */
   public function pay()
   {
       global $db,$request,$comId;
       $fenbiao = getFenbiao($comId,20);
       $userId = (int)$request['user_id'];
       $oid = $request['order_id'];
       $order = $db->get_row("select * from order$fenbiao where id = $oid");

       $product_json = json_decode($order->product_json);
       $subject = '';
       $daizhifu = $order->price;
       $item = [];
       foreach ($product_json as $pdt) {
           $product_item = array(
               'title' => $pdt->title,
               'sn' => $pdt->sn,
               'num' => (int)$pdt->num,
               'price' => (int) $pdt->price_sale
           );
           $subject.=','.$pdt->title.'*'.$pdt->num;
       }
       $body = substr($subject,1);
       $subject = sys_substr($body,30,true);
       $subject = str_replace('_','',$subject);
       $num = (int)$order->pdtNums;
       $price = (int)$order->price;
       try {
           $payer = new Payer();
           $payer->setPaymentMethod('paypal');
           $item = new Item();
           $item->setName($product_item['title']) // 子订单的名称
           ->setDescription($product_item['sn']) // 子订单描述
           ->setCurrency(self::Currency) // 币种
           ->setQuantity($product_item['num']) // 数量
           ->setPrice($product_item['price']); // 价格

           $itemList = new ItemList();
           $itemList->setItems([$item]); // 设置子订单列表

           // 这里是设置运费等
           $details = new Details();
           $details->setShipping(0)
               ->setSubtotal($price);
           // 设置总计费用
           $amount = new Amount();
           $amount->setCurrency(self::Currency)
               ->setTotal($price)
               ->setDetails($details);
           // 创建交易
           $transaction = new Transaction();
           $transaction->setAmount($amount)
               ->setItemList($itemList)
               ->setDescription($subject)
               ->setInvoiceNumber($order->orderId);
           
           // 这里设置支付成功和失败后的跳转链接
           $redirectUrls = new RedirectUrls();

           $redirectUrls->setReturnUrl(self::accept_url.'&orderId='.$order->orderId.'&success=true')
               ->setCancelUrl(self::accept_url.'&orderId='.$order->orderId.'&success=false');

           $payment = new Payment();
           $payment->setIntent('sale')
               ->setPayer($payer)
               ->setRedirectUrls($redirectUrls)
               ->setTransactions([$transaction]);

           $payment->create($this->PayPal);

           // 得到支付链接
           $link = $payment->getApprovalLink();
           $return['code'] = 1;
           $return['message'] = '';
           $return['data'] = array();
           $return['data']['link'] = $link;
           return json_encode($return,JSON_UNESCAPED_UNICODE);
       } catch (HttpException $e) {
           $data = (['msg' => $e->getMessage(), 'code' => $e->getStatusCode(), 'data' => ['order' => ['no' => '1112323231']]]);
           return null;
       }
   }
  • 前端支付密码输入完毕后,发起支付
/**
    * 回调
    */
   public function callback()
   {
       global $db,$request,$comId;
       $fenbiao = getFenbiao($comId,20);
       $success = trim($_GET['success']);

       if ($success == 'false' && !isset($_GET['paymentId']) && !isset($_GET['PayerID'])) {
           echo '取消付款';die;
       }

       $paymentId = trim($_GET['paymentId']);
       $PayerID = trim($_GET['PayerID']);
       $orderId = trim($_GET['orderId']);

       if (!isset($success, $paymentId, $PayerID)) {
           echo '支付失败';die;
       }

       if ((bool)$_GET['success'] === 'false') {
           echo  '支付失败,支付ID【' . $paymentId . '】,支付人ID【' . $PayerID . '】';die;
       }

       $payment = Payment::get($paymentId, $this->PayPal);

       $execute = new PaymentExecution();

       $execute->setPayerId($PayerID);

       try {
           $payment->execute($execute, $this->PayPal);
           //todo 记录
           $db->query("update order10 set paypal_no = '$paymentId', paypal_payerId ='$PayerID' where orderId = '$orderId'");
           //todo 直接跳转前端页面
           $url = '';
           header("localtion:$url");
       } catch (Exception $e) {
           echo ',支付失败,支付ID【' . $paymentId . '】,支付人ID【' . $PayerID . '】';die;
       }
       echo '支付成功,支付ID【' . $paymentId . '】,支付人ID【' . $PayerID . '】';die;
   }
  • 支付后台回调,更改订单状态等
public function notify(){

       //获取回调结果
       $json_data = $this->get_JsonData();

       if(!empty($json_data)){
           Log::debug("paypal notify info:\r\n".json_encode($json_data));
       }else{
           Log::debug("paypal notify fail:参加为空");
       }
       //自己打印$json_data的值看有那些是你业务上用到的
       //比如我用到
       $data['invoice'] = $json_data['resource']['invoice_number'];
       $data['txn_id'] = $json_data['resource']['id'];
       $data['total'] = $json_data['resource']['amount']['total'];
       $data['status'] = isset($json_data['status'])?$json_data['status']:'';
       $data['state'] = $json_data['resource']['state'];

       try {
           //处理相关业务
       } catch (\Exception $e) {
           //记录错误日志
           Log::error("paypal notify fail:".$e->getMessage());

           return "fail";
       }
       return "success";
   }

   public function get_JsonData(){
       $json = file_get_contents('php://input');
       if ($json) {
           $json = str_replace("'", '', $json);
           $json = json_decode($json,true);
       }
       return $json;
   }
  • 退款
/**
    *
    * 根据异步回调的txn_id进行退款操作
    * @param Order $order
    * @return mixed
    */
   public function payPalRefundForTxnId()
   {
       try {
           $txn_id = ‘这个值在回调的时候获取到’;
           $apiContext = new ApiContext(
               new OAuthTokenCredential(
                   ‘client_id’,
                   ‘client_secret’
               ));  // 这里是我们第一步拿到的数据
           // $apiContext->setConfig(['mode' => 'live']);  // live下设置

           $amt = new Amount();
           $amt->setCurrency('USD')
               ->setTotal(100);  // 退款的费用

           $refund = new Refund();
           $refund->setAmount($amt);

           $sale = new Sale();
           $sale->setId($txn_id);
           $refundedSale = $sale->refund($refund, $apiContext);

       } catch (\Exception $e) {
           // PayPal无效退款
           return json_decode(json_encode(['message' => $e->getMessage(), 'code' => $e->getCode(), 'state' => $e->getMessage()]));  // to object
       }
      // 退款完成
       return $refundedSale; 
   }
  • 代码总:
namespace Zhishang;

use PayPal\Api\Payer;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\Details;
use PayPal\Api\Amount;
use PayPal\Api\Transaction;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Payment;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Exception\PayPalConnectionException;
use PayPal\Rest\ApiContext;
use PayPal\Api\PaymentExecution;

class Paypal{
    const clientId = 'AV-SNw2j-1wNqAb1ygg0I4iKT8E8zHT1QCh8eLEbeFZQN2slHhq4dX5pKn4mfz7CHGDhCaVX1N';//ID
    const clientSecret = 'EHjumNJEwf5lkurMyNqo1DJ1v64jsZ9mpFYlKIkVQBh6Kksygxx_u77F8G0rSQHW9Awezdqd4RCd';//秘钥
    const accept_url = 'http://fumei.com:8082/service.php?action=paypal_callback&com_id=1210';//返回地址
    const Currency = 'USD';//币种
    protected $PayPal;

    public function __construct()
    {
        $this->PayPal = new ApiContext(
            new OAuthTokenCredential(
                self::clientId,
                self::clientSecret
            )
        );
        //如果是沙盒测试环境不设置,请注释掉
//        $this->paypal->setConfig(
//            array(
//                'mode' => 'sandbox',
//            )
//        );

//        $this->PayPal->setConfig([
//            'mode' => 'sandbox',
//            'log.LogEnabled' => true,
//            'log.FileName' => 'logs/PayPal.log',
//            'log.LogLevel' => 'DEBUG', // PLEASE USE `INFO` LEVEL FOR LOGGING IN LIVE ENVIRONMENTS
//            'cache.enabled' => true,
//        ]);
        $this->PayPal->setConfig(
            array(
                'mode' => 'sandbox',
                'log.LogEnabled' => true,
                'log.FileName' => '../PayPal.log',
                'log.LogLevel' => 'DEBUG', // PLEASE USE `INFO` LEVEL FOR LOGGING IN LIVE ENVIRONMENTS
                'cache.enabled' => true
            )
        );
    }

    /**
     * @param
     * $product 商品
     * $price 价钱
     * $shipping 运费
     * $description 描述内容
     */
    public function pay()
    {
        global $db,$request,$comId;
        $fenbiao = getFenbiao($comId,20);
        $userId = (int)$request['user_id'];
        $oid = $request['order_id'];
        $order = $db->get_row("select * from order$fenbiao where id = $oid");

        $product_json = json_decode($order->product_json);
        $subject = '';
        $daizhifu = $order->price;
        $item = [];
        foreach ($product_json as $pdt) {
            $product_item = array(
                'title' => $pdt->title,
                'sn' => $pdt->sn,
                'num' => (int)$pdt->num,
                'price' => (int) $pdt->price_sale
            );
            $subject.=','.$pdt->title.'*'.$pdt->num;
        }
        $body = substr($subject,1);
        $subject = sys_substr($body,30,true);
        $subject = str_replace('_','',$subject);
        $num = (int)$order->pdtNums;
        $price = (int)$order->price;
        try {
            $payer = new Payer();
            $payer->setPaymentMethod('paypal');
            $item = new Item();
            $item->setName($product_item['title']) // 子订单的名称
            ->setDescription($product_item['sn']) // 子订单描述
            ->setCurrency(self::Currency) // 币种
            ->setQuantity($product_item['num']) // 数量
            ->setPrice($product_item['price']); // 价格

            $itemList = new ItemList();
            $itemList->setItems([$item]); // 设置子订单列表

            // 这里是设置运费等
            $details = new Details();
            $details->setShipping(0)
                ->setSubtotal($price);
            // 设置总计费用
            $amount = new Amount();
            $amount->setCurrency(self::Currency)
                ->setTotal($price)
                ->setDetails($details);
            // 创建交易
            $transaction = new Transaction();
            $transaction->setAmount($amount)
                ->setItemList($itemList)
                ->setDescription($subject)
                ->setInvoiceNumber($order->orderId);
            
            // 这里设置支付成功和失败后的跳转链接
            $redirectUrls = new RedirectUrls();

            $redirectUrls->setReturnUrl(self::accept_url.'&orderId='.$order->orderId.'&success=true')
                ->setCancelUrl(self::accept_url.'&orderId='.$order->orderId.'&success=false');

            $payment = new Payment();
            $payment->setIntent('sale')
                ->setPayer($payer)
                ->setRedirectUrls($redirectUrls)
                ->setTransactions([$transaction]);

            $payment->create($this->PayPal);

            // 得到支付链接
            $link = $payment->getApprovalLink();
            $return['code'] = 1;
            $return['message'] = '';
            $return['data'] = array();
            $return['data']['link'] = $link;
            return json_encode($return,JSON_UNESCAPED_UNICODE);
        } catch (HttpException $e) {
            $data = (['msg' => $e->getMessage(), 'code' => $e->getStatusCode(), 'data' => ['order' => ['no' => '1112323231']]]);
            return null;
        }
    }

    /**
     * 回调
     */
    public function callback()
    {
        global $db,$request,$comId;
        $fenbiao = getFenbiao($comId,20);
        $success = trim($_GET['success']);

        if ($success == 'false' && !isset($_GET['paymentId']) && !isset($_GET['PayerID'])) {
            echo '取消付款';die;
        }

        $paymentId = trim($_GET['paymentId']);
        $PayerID = trim($_GET['PayerID']);
        $orderId = trim($_GET['orderId']);

        if (!isset($success, $paymentId, $PayerID)) {
            echo '支付失败';die;
        }

        if ((bool)$_GET['success'] === 'false') {
            echo  '支付失败,支付ID【' . $paymentId . '】,支付人ID【' . $PayerID . '】';die;
        }

        $payment = Payment::get($paymentId, $this->PayPal);

        $execute = new PaymentExecution();

        $execute->setPayerId($PayerID);

        try {
            $payment->execute($execute, $this->PayPal);
            //todo 记录
            $db->query("update order10 set paypal_no = '$paymentId', paypal_payerId ='$PayerID' where orderId = '$orderId'");
        } catch (Exception $e) {
            echo ',支付失败,支付ID【' . $paymentId . '】,支付人ID【' . $PayerID . '】';die;
        }
        echo '支付成功,支付ID【' . $paymentId . '】,支付人ID【' . $PayerID . '】';die;
    }

    public function notify(){

        //获取回调结果
        $json_data = $this->get_JsonData();

        if(!empty($json_data)){
            Log::debug("paypal notify info:\r\n".json_encode($json_data));
        }else{
            Log::debug("paypal notify fail:参加为空");
        }
        //自己打印$json_data的值看有那些是你业务上用到的
        //比如我用到
        $data['invoice'] = $json_data['resource']['invoice_number'];
        $data['txn_id'] = $json_data['resource']['id'];
        $data['total'] = $json_data['resource']['amount']['total'];
        $data['status'] = isset($json_data['status'])?$json_data['status']:'';
        $data['state'] = $json_data['resource']['state'];

        try {
            //处理相关业务
        } catch (\Exception $e) {
            //记录错误日志
            Log::error("paypal notify fail:".$e->getMessage());

            return "fail";
        }
        return "success";
    }

    public function get_JsonData(){
        $json = file_get_contents('php://input');
        if ($json) {
            $json = str_replace("'", '', $json);
            $json = json_decode($json,true);
        }
        return $json;
    }
}

注意事项:

  • 正式站:当你切换到正式环境去测试支付的时候不要拿0.1美元去做测试,因为你的支付完毕之后,他在商户管理那边不会显示,可能是因为手续费等不知名问题,请尝试1美元。