It doesn鈥檛 appear possible to pass paymentSourceId into the payment/pay action as examples show. We got some generic errors whilst trying to use this and it turns out the API requests being made are missing the token (in the payment form).
Looking through the controller, there鈥檚 only one reference of paymentSourceId that I could find and this is only for newly created payment sources.
It鈥檚 super unclear how this works. Are we supposed to pass the token in via the body parameters? Or change our flow to update the order in a step beforehand?
Okay, I鈥檓 gathering that the example is out of date and that it鈥檚 no longer possible to pass paymentSourceId to payment/pay.
The payment source isn鈥檛 loaded and populateFromPaymentSource never gets called in this example.
<form method="post">
{{ csrfInput() }}
{{ actionInput('commerce/payments/pay') }}
{{ hiddenInput('gatewayId', gateway.id) }}
{{ redirectInput('/payment/complete') }}
{# @var paymentSource \craft\commerce\models\PaymentSource #}
{% for paymentSource in paymentSources %}
<div>
<input type="radio" name="paymentSourceId" value="{{ paymentSource.id }}">
{{ paymentSource.description }}
</div>
{% endfor %}
<input type="submit">
</form>
We all expected passing paymentSourceId to the payment/pay action would update the payment form with that source.
The workaround seems to be to pass the payment form values directly (e.g. token instead of paymentFormId).
@lukeholder This doesn鈥檛 seem right. Are you able to confirm is this is the correct approach?
No, it can鈥檛 be right. The payment form requires a customer reference too. We _have_ to call populateFromPaymentSource.
We should be updating the order here if the paymentSourceId was passed in.
if ($paymentSourceId = $request->getBodyParam('paymentSourceId') !== null) {
$order->paymentSourceId = $paymentSourceId;
}
$paymentSource = $order->getPaymentSource();
Adding those lines make everything work.
Or better yet, just use the same code as the cart/update-cart action uses.
// Set Payment Source on cart
if ($paymentSourceId = $request->getParam('paymentSourceId')) {
$this->_cart->gatewayId = null;
$this->_cart->paymentSourceId = $paymentSourceId;
}
For others encountering this, we鈥檝e created a separate controller extending the base PaymentsController to set the payment source.
<?php
namespace modules\payments\controllers;
use Craft;
use craft\commerce\Plugin as Commerce;
/**
* Class PaymentsController
*
* @package modules\payments\controllers
*/
class PaymentsController extends \craft\commerce\controllers\PaymentsController
{
/**
* @return \yii\web\Response|null
* @throws \Throwable
* @throws \craft\errors\ElementNotFoundException
* @throws \yii\base\Exception
* @throws \yii\web\HttpException
*/
public function actionPay()
{
// Override the pay action to add the payment source to the order
// https://github.com/craftcms/commerce/issues/1283
$paymentSourceId = Craft::$app->getRequest()->getBodyParam('paymentSourceId');
if ($paymentSourceId) {
$order = Commerce::getInstance()->getCarts()->getCart(true);
$order->gatewayId = null;
$order->paymentSourceId = $paymentSourceId;
Craft::$app->getElements()->saveElement($order);
}
return parent::actionPay();
}
}
@lukeholder Did you have any thoughts on this?
i just encountered this issue. had to solve by updating the cart (ajax call) with the paymentSourceId before submitting.
Your solution is a clever workaround. It would be nice if it was consistent with how gatewayId is handled however.
@lukeholder You mention here that this is possible.
Fixed in Commerce 3.1 due out this week.
Most helpful comment
Or better yet, just use the same code as the
cart/update-cartaction uses.For others encountering this, we鈥檝e created a separate controller extending the base
PaymentsControllerto set the payment source.