Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PxPay Card Tokens #158

Open
mattsagen opened this issue Feb 14, 2017 · 9 comments
Open

PxPay Card Tokens #158

mattsagen opened this issue Feb 14, 2017 · 9 comments

Comments

@mattsagen
Copy link

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 thephpleague/omnipay-paymentexpress#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();

@bummzack
Copy link
Collaborator

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.

@mattsagen
Copy link
Author

mattsagen commented Feb 16, 2017

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?

@bummzack
Copy link
Collaborator

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?

@mattsagen
Copy link
Author

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 :)

@mattsagen
Copy link
Author

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?

@bummzack
Copy link
Collaborator

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).

@mattsagen
Copy link
Author

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...

@cjsewell
Copy link
Contributor

cjsewell commented Mar 1, 2017

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);
            }
        }
    }
}

@bummzack
Copy link
Collaborator

bummzack commented Mar 1, 2017

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:

  • Is the Gateway correct? ($response->getPayment()->Gateway == 'Expected_Gateway'). You should do this, or your code is going to fail when using different gateways.
  • If you need the OmnipayResponse, always check for a null value first. There are cases where a ServiceResponse does not contain an OmnipayResponse.
  • You also might to check the different flags on the $response, such as isError or isNotification, depending on what you need to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants