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
相关产品推荐
相关产品推荐

