You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Symfony 3.4集成PayumBundle实现PayPal余额充值及订单扣款问题

嘿,我来帮你一步步搞定Symfony 3.4集成PayumBundle实现PayPal快速结账的需求!咱们分模块来,先搭好基础配置,再处理充值和订单的核心逻辑。

一、基础配置:安装PayumBundle并对接PayPal

首先通过Composer安装依赖包,Symfony 3.4兼容的版本如下:

composer require payum/payum-bundle payum/paypal-express-checkout-nvp

1. 注册Bundle到AppKernel

打开app/AppKernel.php,把PayumBundle加入注册列表:

public function registerBundles()
{
    $bundles = [
        // ... 其他已注册的Bundle
        new Payum\Bundle\PayumBundle\PayumBundle(),
    ];
    return $bundles;
}

2. 配置PayPal API参数

在app/config/config.yml里添加Payum的核心配置,包括存储实体和PayPal网关:

payum:
    security:
        token_storage:
            AppBundle\Entity\PaymentToken:
                doctrine:
                    driver: orm
    storages:
        AppBundle\Entity\Payment:
            doctrine:
                driver: orm
    gateways:
        paypal_express_checkout:
            factory: paypal_express_checkout
            username: '%paypal_username%'
            password: '%paypal_password%'
            signature: '%paypal_signature%'
            sandbox: true # 测试环境用true,正式上线改为false

然后在app/config/parameters.yml里补充PayPal的API密钥(这些密钥可以从PayPal开发者后台获取):

parameters:
    # ... 其他参数
    paypal_username: '你的PayPal API用户名'
    paypal_password: '你的PayPal API密码'
    paypal_signature: '你的PayPal API签名'

3. 创建Payum所需的实体

Payum需要两个实体来存储支付信息和令牌,分别创建:

Payment实体

// src/AppBundle/Entity/Payment.php
namespace AppBundle\Entity;

use Payum\Core\Model\Payment as BasePayment;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="payments")
 */
class Payment extends BasePayment
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     * @ORM\Column(type="integer")
     */
    protected $id;
}

PaymentToken实体

// src/AppBundle/Entity/PaymentToken.php
namespace AppBundle\Entity;

use Payum\Core\Model\Token;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="payment_tokens")
 */
class PaymentToken extends Token
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     * @ORM\Column(type="integer")
     */
    protected $id;
}

最后执行数据库迁移,生成对应的表:

php bin/console doctrine:schema:update --force
二、实现账户充值功能(PayPal支付后自动加余额)

1. 创建充值表单

先做一个简单的表单让用户输入充值金额:

// src/AppBundle/Form/Type/RechargeType.php
namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints as Assert;

class RechargeType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('amount', \Symfony\Component\Form\Extension\Core\Type\MoneyType::class, [
                'label' => '充值金额',
                'currency' => 'USD', // 按需修改为CNY等其他货币
                'constraints' => [
                    new Assert\NotBlank(),
                    new Assert\GreaterThan(0)
                ]
            ]);
    }
}

2. 充值控制器逻辑

创建处理表单提交、生成PayPal支付链接,以及支付回调的控制器:

// src/AppBundle/Controller/RechargeController.php
namespace AppBundle\Controller;

use AppBundle\Entity\Payment;
use AppBundle\Form\Type\RechargeType;
use Payum\Core\Request\GetHumanStatus;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;

/**
 * @Security("is_granted('ROLE_USER')")
 */
class RechargeController extends Controller
{
    public function indexAction(Request $request)
    {
        $form = $this->createForm(RechargeType::class);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $amount = $form->get('amount')->getData();
            $user = $this->getUser();

            // 创建支付记录
            $payment = new Payment();
            $payment->setNumber(uniqid());
            $payment->setCurrencyCode('USD');
            $payment->setTotalAmount($amount * 100); // Payum以"分"为单位,需乘以100
            $payment->setDescription('用户'.$user->getUsername().'账户充值');
            $payment->setClientId($user->getId());
            $payment->setClientEmail($user->getEmail());

            $em = $this->getDoctrine()->getManager();
            $em->persist($payment);
            $em->flush();

            // 生成PayPal支付令牌和跳转链接
            $token = $this->get('payum')->getTokenFactory()->createCaptureToken(
                'paypal_express_checkout',
                $payment,
                'recharge_done' // 支付完成后的回调路由
            );

            return $this->redirect($token->getTargetUrl());
        }

        return $this->render('recharge/index.html.twig', [
            'form' => $form->createView()
        ]);
    }

    public function doneAction(Request $request)
    {
        // 验证支付令牌
        $token = $this->get('payum')->getHttpRequestVerifier()->verify($request);
        $gateway = $this->get('payum')->getGateway($token->getGatewayName());

        // 获取支付状态
        $gateway->execute($status = new GetHumanStatus($token));

        if ($status->isCaptured()) {
            // 支付成功,更新用户余额
            $payment = $token->getDetails();
            $user = $this->getDoctrine()->getRepository('AppBundle:User')->find($payment->getClientId());

            if ($user) {
                $rechargeAmount = $payment->getTotalAmount() / 100; // 转换回元单位
                $user->setBalance($user->getBalance() + $rechargeAmount);

                $em = $this->getDoctrine()->getManager();
                $em->persist($user);
                $em->flush();

                $this->addFlash('success', '充值成功!账户余额已更新');
            } else {
                $this->addFlash('error', '用户不存在,充值失败');
            }
        } elseif ($status->isCanceled()) {
            $this->addFlash('warning', '你取消了支付');
        } else {
            $this->addFlash('error', '支付失败,请重试');
        }

        // 失效令牌,避免重复处理
        $this->get('payum')->getHttpRequestVerifier()->invalidate($token);

        return $this->redirectToRoute('recharge_index');
    }
}

3. 配置路由和模板

在app/config/routing.yml里添加充值相关路由:

recharge_index:
    path: /recharge
    defaults: { _controller: AppBundle:Recharge:index }

recharge_done:
    path: /recharge/done
    defaults: { _controller: AppBundle:Recharge:done }

创建充值页面模板app/Resources/views/recharge/index.html.twig:

{% extends 'base.html.twig' %}

{% block body %}
    <h2>账户充值</h2>

    {% for flash_message in app.flashes('success') %}
        <div class="alert alert-success">{{ flash_message }}</div>
    {% endfor %}
    {% for flash_message in app.flashes('error') %}
        <div class="alert alert-danger">{{ flash_message }}</div>
    {% endfor %}
    {% for flash_message in app.flashes('warning') %}
        <div class="alert alert-warning">{{ flash_message }}</div>
    {% endfor %}

    {{ form_start(form) }}
        {{ form_widget(form) }}
        <button type="submit" class="btn btn-primary mt-3">前往PayPal支付</button>
    {{ form_end(form) }}
{% endblock %}
三、实现订单购买功能(从余额扣除金额)

假设你已经有Order实体(包含amount、user、status等字段),核心逻辑是验证余额、创建订单并扣减余额,记得用事务保证数据一致性:

// src/AppBundle/Controller/OrderController.php
namespace AppBundle\Controller;

use AppBundle\Entity\Order;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;

/**
 * @Security("is_granted('ROLE_USER')")
 */
class OrderController extends Controller
{
    public function createAction(Request $request)
    {
        $user = $this->getUser();
        $orderAmount = 49.99; // 可以从表单或商品接口获取实际金额

        // 验证余额是否足够
        if ($user->getBalance() < $orderAmount) {
            $this->addFlash('error', '账户余额不足,请先充值');
            return $this->redirectToRoute('recharge_index');
        }

        $em = $this->getDoctrine()->getManager();
        try {
            $em->beginTransaction();

            // 创建订单
            $order = new Order();
            $order->setUser($user);
            $order->setAmount($orderAmount);
            $order->setStatus('completed'); // 按需设置订单状态

            // 扣减余额
            $user->setBalance($user->getBalance() - $orderAmount);

            $em->persist($order);
            $em->persist($user);
            $em->flush();
            $em->commit();

            $this->addFlash('success', '订单创建成功!余额已扣除');
        } catch (\Exception $e) {
            $em->rollback();
            $this->addFlash('error', '订单创建失败:'.$e->getMessage());
        }

        return $this->redirectToRoute('order_list');
    }
}
四、关键注意事项
  • PayPal沙箱测试:一定要用PayPal开发者后台的测试账号进行支付测试,不要用真实账号。
  • 金额单位:Payum所有金额都以分为单位,处理时记得乘以/除以100转换。
  • 安全防护:确保充值和订单接口只有登录用户能访问,可通过@Security注解或路由配置实现。
  • 日志记录:建议在支付回调和订单处理中添加日志,方便后续排查问题,比如:
    $this->get('logger')->info('用户'.$user->getId().'充值成功,金额:'.$rechargeAmount);
    

内容的提问来源于stack exchange,提问作者Ayoub_B

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 10:11:18