I've been trying to wrap my head around on how to implement a classic offline payment gateway using Sylius (sylius/sylius-standard, v0.16).
So far I got this in my bundle's build() method:
public function build(ContainerBuilder $container)
{
/** @var PayumExtension $payum */
$payum = $container->getExtension('payum');
$payum->addGatewayFactory(new PayWayOffsiteGatewayFactory());
}
Here's my config.yml:
sylius_payment:
gateways:
payway_offsite: PayWay
payum:
gateways:
payway_offsite:
payway_offsite:
secret_key: %tcompayway.secret_key%
shop_id: %tcompayway.shop_id%
username: %tcompayway.username%
password: %tcompayway.password%
sandbox: %tcompayway.sandbox%
The %compayway.*% fields are configured okay ofcourse, skipping this part. Then I got this in my PayWayOffsiteGatewayFactory class:
class PayWayOffsiteGatewayFactory extends AbstractGatewayFactory
{
/**
* {@inheritdoc}
*/
public function getName()
{
return 'payway_offsite';
}
/**
* {@inheritdoc}
*/
public function addConfiguration(ArrayNodeDefinition $builder)
{
parent::addConfiguration($builder);
$builder->children()
->scalarNode('secret_key')->isRequired()->cannotBeEmpty()->end()
->scalarNode('shop_id')->isRequired()->cannotBeEmpty()->end()
->scalarNode('username')->isRequired()->cannotBeEmpty()->end()
->scalarNode('password')->isRequired()->cannotBeEmpty()->end()
->booleanNode('sandbox')->defaultTrue()->end()
->end();
}
/**
* {@inheritDoc}
*/
protected function getPayumGatewayFactoryClass()
{
return 'WebBurza\PaymentBundle\Payum\PayWay\PayWayGatewayFactory';
}
/**
* {@inheritDoc}
*/
protected function getComposerPackage()
{
return 'webburza/tcompayway';
}
}
And the PayWayGatewayFactory class:
class PayWayGatewayFactory extends GatewayFactory
{
/**
* {@inheritDoc}
* @throws \Payum\Core\Exception\InvalidArgumentException
*/
protected function populateConfig(ArrayObject $config)
{
$config->defaults(array(
'payum.factory_name' => 'payway_offsite',
'payum.factory_title' => 'PayWay Offsite',
'payum.action.capture' => new CaptureOffsiteAction(),
'payum.action.capture_null' => new CaptureOffsiteNullAction(),
'payum.action.status' => new StatusAction(),
'payum.action.convert_payment' => new ConvertPaymentAction(),
));
if (false == $config['payum.api']) {
$config['payum.default_options'] = array(
'secret_key' => '',
'shop_id' => '',
'username' => '',
'password' => '',
'sandbox' => true
);
$config->defaults($config['payum.default_options']);
$config['payum.required_options'] = [
'secret_key',
'shop_id',
'username',
'password',
'sandbox'
];
$config['payum.api'] = function (ArrayObject $config) {
$config->validateNotEmpty($config['payum.required_options']);
$api = new Api([
'secret_key'=> $config['secret_key'],
'shop_id' => $config['shop_id'],
'username' => $config['username'],
'password' => $config['password'],
'sandbox' => $config['sandbox'],
], $config['payum.http_client']);
return $api;
};
}
}
}
So far, I'm under the impression all this is more-less okay (correct me if I'm wrong). I've managed to get to my CaptureOffsiteAction's execute method:
public function execute($captureRequest)
{
RequestNotSupportedException::assertSupports($this, $captureRequest);
$model = ArrayObject::ensureArrayObject($captureRequest->getModel());
$httpRequest = new GetHttpRequest();
$this->gateway->execute($httpRequest);
if ($httpRequest->method === 'POST') {
$status = new GetHumanStatus($model);
if (isset($httpRequest->request['pgw_transaction_id']) && $httpRequest->request['pgw_transaction_id'] > 0) {
// mark this order id as paid, convert cart to order, etc.
} else {
// mark as failed/cancelled, redirect back to cart
}
return;
}
$model['successUrl'] = $captureRequest->getToken()->getTargetUrl();
$model['failureUrl'] = $captureRequest->getToken()->getTargetUrl();
$offsiteUrl = $this->api->getOffsiteUrl();
$data = $this->api->prepareOffsitePayment($model->toUnsafeArray());
$headers = ['application/x-www-form-urlencoded'];
throw new HttpPostRedirect(
$offsiteUrl,
$data,
200,
$headers
);
}
First, lets see if I got this right - once I start the capture process (execute method) - things work as expected, I end up in the bottom part of the method, creating success and failure URLs, preparing offsite payment, hash and all that and eventually end up on the payment gateway via HttpPostRedirect with correct order id, price etc. This part is okay.
After paying, upon returning to success url, I again end up in CaptureOffsiteAction::execute and check for pgw_transaction_id - if this is set, it means i got back from payment and I should detect either a successfully paid order or cancelled/failed payment.
Questions are:
in case of successful payment - how do I 'close' the order (convert cart to order, mark payment as captured, etc.?)
I see in other payment gateways that there's plenty other methos - lets say im interested in only offsite payment via post-redirect - what does StatusAction do? And when is it called?
So far I found Payum (and PayumBundle) very confusing, I've managed to implement 2 offsite gateways in under a day using OmniPay (on other, non-Sylius related projects) but can't seem to solve this Payum thing. :(
Any help appreciated, thanks in advance!
Sorry for late reply - I managed to figure out where I went wrong.
Whole idea is in sequential calling of Actions. In my case, that would be:
My first error was thinking that Payum uses only 1 action for a specific workflow. Documentation was a bit confusing (or at least it seemed like that to me), so I didn't get the part where StatusAction actually takes care of handling order status upon return from PG.
So from what I gathered:
Here's my StatusAction execute method for example:
public function execute($request)
{
RequestNotSupportedException::assertSupports($this, $request);
$model = new ArrayObject($request->getModel());
if (null === $model['EXECCODE']) {
$request->markNew();
return;
}
if (Api::STATUS_SUCCESS === $model['EXECCODE']) {
$request->markCaptured();
return;
}
$request->markFailed();
}
And here's the final CaptureOffsiteAction::execute method:
public function execute($captureRequest)
{
RequestNotSupportedException::assertSupports($this, $captureRequest);
$model = ArrayObject::ensureArrayObject($captureRequest->getModel());
$httpRequest = new GetHttpRequest();
$this->gateway->execute($httpRequest);
if ($httpRequest->method === 'POST') {
// TODO: properly validate response signature here
if (isset($httpRequest->request['pgw_transaction_id'])) {
$model['EXECCODE'] = Api::STATUS_SUCCESS;
} else {
$model['EXECCODE'] = $httpRequest->request['pgw_result_code'];
}
return;
}
$model['successUrl'] = $captureRequest->getToken()->getTargetUrl();
$model['failureUrl'] = $captureRequest->getToken()->getTargetUrl();
$offsiteUrl = $this->api->getOffsiteUrl();
$data = $this->api->prepareOffsitePayment($model->toUnsafeArray(), $captureRequest);
$headers = ['application/x-www-form-urlencoded'];
throw new HttpPostRedirect(
$offsiteUrl,
$data,
200,
$headers
);
}
Hope this helps some of you! Cheers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With