Open mattsagen opened 7 years ago
It seems like you'd need to create a CC via PxPayGateway::createCard
first. Then use the Token you get from this action for subsequent purchase requests. From what I can tell, you should use the PaymentExpress_PxPost
Gateway for subsequent requests instead, as you don't want the user to be redirected anymore…
This is a gateway-specific method though and not part of the silverstripe-omnipay
wrapper. You could implement this as a service, see https://github.com/silverstripe/silverstripe-omnipay#the-payment-services-and-service-factory
Custom service implementations can be registered via Config API, something like this:
ServiceFactory:
services:
createCard: 'Fullly\Qualified\ClassName\MyCreateCardService'
Then you can use: ServiceFactory::create()->getService($payment, 'createCard');
to get a Service instance.
I'm not familiar with the PxPay specification to give advice where to store the token though… Payment
seems to be the wrong place. Maybe store them in a separate table?
TL/DR The createCard
action of the PxPay Gateway isn't supported out of the box, but you can integrate it yourself and configure silverstripe-omnipay
to seamlessly work with your code. PRs are welcome of course. Or you could start a separate Module that adds PxPay specific functionality to silverstripe-omnipay
.
Thanks, Bummzack - So easy enough to set up a new service based on PurchaseService, and then to change the call to the gateway from purchase to createCard. Now a bit stuck on where to deal with the response to store the token from DPS - I thought I could just extend the onCaptured behaviour, but although I can see a payment status change to Captured, the event never fires... ` class ABCPayment extends DataExtension {
public function onCaptured($response) {
$content = "Response: ";
$fp = fopen($_SERVER['DOCUMENT_ROOT'] . "/text.txt","wb");
fwrite($fp,$content);
fclose($fp);
}
} `
I'm not using a Payable extension and hadn't really decided what datamodel to employ (considered just adding it to Payment, and wouldn't mind hearing your thoughts about why not to). Any idea why that never gets called, or any idea of a better place to do that from?
I'd strongly suggest against using the purchase behavior, as Captured
should be a status reserved to actual purchases. But I guess for initial testing it's fine… just consider moving to separate states later on (eg. PendingCardCreated
and CardCreated
or similar).
As previously stated: I'm not really familiar with the PxPay gateways. To me, it seems like you can just create a Card (eg. get a Token for a CC), without performing an actual purchase. Then you can use the token to authorize multiple Purchases.
If that's the case, it might be better to store the token with the current Member
? Otherwise you'd have to search for the most recent Payment
for a Member
that has a token to get the Token you need?
The onCaptured
hook should fire (especially if the Payment status changed to Captured
). Are you sure you applied the extension via config and did a dev/build
?
Thanks again bummzack, I think maybe I overcomplicated my question.
I could have sworn I built after adding the extension, but I must not have, because it's firing thank goodness!
It makes total sense for us to use the purchase behaviour during the store card, which appears to be supported by DPS, because then the customer only ever sees an actual purchase for a real amount.
I can now see DpsBillingId coming back in the response, so I think all is well. Now I just need to get familiar with PxPost, because when I simply switched to the SS purchase service, and supplied the DpsBillingId, I got a "The number parameter is required" error... inching closer anyway :)
Ok, my custom create card service is working - last remaining issue is that I don't see any onCancelled() events firing - the onComplete($result) is sweet and is storing the card token and notifying our app (we run payments as a microservice) but I can't seem to get the onCancelled() which would be much preferred to trusting success/fail URL... it's on the same data extension as the working onComplete()... any known issues/gotchas?
Wait… aren't you working with @cjsewell? He just implemented createCard
, see #161.
The extension should be added to Payment
and yes, onCancelled
should work (when the payment was cancelled on the external payment form).
No, I didn't see that he was working on that at the same time... nearly identical ... but I have tried with his as well and I can't get the onCancelled() to ever be called - this is when clicking "Cancel" in the hosted payment page. It redirects to the failure URL, but no event. Our CustomPayment extension is correctly added to Payment, it extends DataExtension, and onComplete($response) works within the same class...
Hey @mattsagen
I had the same issue, this isnt a silverstripe-omnipay issue really. Its due to the fact that PXPay does not support a cancel url/event. And the main DPS Omnipay packge sets both the fail and success urls to the return url https://github.com/thephpleague/omnipay-paymentexpress/blob/master/src/Message/PxPayAuthorizeRequest.php#L196
It returns as complete, but with an error....
They only way to catch this as far as I can tell with PXPay is to use the updateServiceResponse
extension hook and check the response yourself.
By testing, it appears when you cancel a payment, the response back from DPS sets the $cardHolderName
to "User Cancelled" and the $responseCode
to "RC"
SilverStripe\Omnipay\Service\PaymentService:
extensions:
- PaymentServiceExtension
class PaymentServiceExtension extends Extension
{
function updateServiceResponse(SilverStripe\Omnipay\Service\ServiceResponse $response)
{
$dpsResponse = $response->getOmnipayResponse()->getData();
if ($dpsResponse instanceof SimpleXMLElement) {
$cardHolderName = (string) $dpsResponse->CardHolderName;
$success = $dpsResponse->Success == 1;
$responseText = (string) $dpsResponse->ResponseText;
$responseCode = (string) $dpsResponse->ReCo;
if (!$success && $responseCode == 'RC' && $cardHolderName == 'User Cancelled') {
// User canceled?
Debug::dump('CardHolderName: '.$cardHolderName);
Debug::dump('Success: '.($success ? 'true' : 'false'));
Debug::dump('ResponseText: '.$responseText);
Debug::dump('ReCo: '.$responseCode);
}
}
}
}
I'll look into this… is a "cancelled" payment really considered to be "successful" by the PxPay Gateway? It should at least be marked as erroneous?
Please note: All ServiceResponse
objects are going through updateServiceResponse
. When using updateServiceResponse
always check:
$response->getPayment()->Gateway == 'Expected_Gateway'
). You should do this, or your code is going to fail when using different gateways.null
value first. There are cases where a ServiceResponse
does not contain an OmnipayResponse.$response
, such as isError
or isNotification
, depending on what you need to do.
Hi there, thx for the awesome work. I am trying to use PaymentExpress_PxPay to store cards in offsite hosted gateway. I am not sure how to do this through the Silverstripe Wrapper as I can't find an example?
But before I spend time on it, I just want to be sure that after we have created a card through PxPay during a purchase, we can provide the amount, currency, and token via PxPay and not redirect the customer to the offsite gateway. It seems obvious that this is how it would work, otherwise tokens aren't much use (we are using for subscriptions and one-click purchases) but I want to make sure.
If this is correct use case, then could someone provide an example of create card call from within a SS Controller? I'm thinking that I might have to do away with the wrapper and just use the raw Omnipay classes like https://github.com/thephpleague/omnipay-paymentexpress/issues/1 but would rather have handy SS integration...
After succesfully doing normal test purchases I tried adding the EnableAddBillCard with a BillingID, then changing the transaction number, removing EnableAddBillCard, and trying again, but no luck.
$payment = Payment::create() ->init("PaymentExpress_PxPay", 100, "NZD") ->setSuccessUrl($this->Link()."/success") ->setFailureUrl($this->Link()."/cancelled"); // Save it to the database to generate an ID $payment->write(); $data = ['transactionId' => '125f8s3d','EnableAddBillCard' => 1,'BillingID' => 'xyz555']; $response = ServiceFactory::create() ->getService($payment, ServiceFactory::INTENT_PURCHASE) ->initiate($data); return $response->redirectOrRespond();