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

Updated to work with Payfast's latest API #1

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Gateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function getDefaultParameters()
'merchantId' => '',
'merchantKey' => '',
'pdtKey' => '',
'testMode' => false,
'testMode' => false
);
}

Expand Down
12 changes: 12 additions & 0 deletions src/Message/CompletePurchaseItnResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
*/
class CompletePurchaseItnResponse extends AbstractResponse
{
/**
* @param RequestInterface $request
* @param $status
*/
public function __construct(RequestInterface $request, $data, $status)
{
parent::__construct($request, $data);
Expand All @@ -18,6 +22,14 @@ public function __construct(RequestInterface $request, $data, $status)

public function isSuccessful()
{
if ($this->isValid() && isset($this->data['payment_status'])) {
return $this->data['payment_status'] === 'COMPLETE';
} else {
return false;
}
}

public function isValid() {
return 'VALID' === $this->status;
}

Expand Down
206 changes: 193 additions & 13 deletions src/Message/PurchaseRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,195 @@ public function setPdtKey($value)
return $this->setParameter('pdtKey', $value);
}

public function getMPaymentId()
{
return $this->getParameter('mPaymentId');
}

public function setMPaymentId($value)
{
return $this->setParameter('mPaymentId', $value);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this field supposed to be used for? It sounds like the existing getTransactionId() may be helpful.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field was modified to match payfast documentation. When the PurchaseRequst class is called static function initialize looks for any parameters which do not match a setter and if one does not match its ignored. No need to over complicate the matter mixing omnipays fields with payfast fields. Why make the user study payfasts documentation and then have to figure out how to use omnipay so they know which fields to set in order for the output to match payfasts.


public function getPassPhrase()
{
return $this->getParameter('passPhrase');
}

public function setPassPhrase($value)
{
return $this->setParameter('passPhrase', $value);
}

public function getItemDescription()
{
return $this->getParameter('item_description');
}

public function setItemDescription($value)
{
return $this->setParameter('item_description', $value);
}

public function getCustomStr1()
{
return $this->getParameter('customStr1');
}

public function setCustomStr1($value)
{
return $this->setParameter('customStr1', $value);
}

public function getCustomStr2()
{
return $this->getParameter('customStr2');
}

public function setCustomStr2($value)
{
return $this->setParameter('customStr2', $value);
}

public function getCustomStr3()
{
return $this->getParameter('customStr3');
}

public function setCustomStr3($value)
{
return $this->setParameter('customStr3', $value);
}

public function getCustomStr4()
{
return $this->getParameter('customStr4');
}

public function setCustomStr4($value)
{
return $this->setParameter('customStr4', $value);
}

public function getCustomStr5()
{
return $this->getParameter('customStr5');
}

public function setCustomStr5($value)
{
return $this->setParameter('customStr5', $value);
}

public function getCustomInt1()
{
return $this->getParameter('customInt1');
}

public function setCustomInt1($value)
{
return $this->setParameter('customInt1', $value);
}

public function getCustomInt2()
{
return $this->getParameter('customInt2');
}

public function setCustomInt2($value)
{
return $this->setParameter('customInt2', $value);
}

public function getCustomInt3()
{
return $this->getParameter('customInt3');
}

public function setCustomInt3($value)
{
return $this->setParameter('customInt3', $value);
}

public function getCustomInt4()
{
return $this->getParameter('customInt4');
}

public function setCustomInt4($value)
{
return $this->setParameter('customInt4', $value);
}

public function getCustomInt5()
{
return $this->getParameter('customInt5');
}

public function setCustomInt5($value)
{
return $this->setParameter('customInt5', $value);
}

public function getEmailConfirmation()
{
return $this->getParameter('emailConfirmation');
}

public function setEmailConfirmation($value)
{
return $this->setParameter('emailConfirmation', $value);
}

public function getConfirmationAddress()
{
return $this->getParameter('confirmationAddress');
}

public function setConfirmationAddress($value)
{
return $this->setParameter('confirmationAddress', $value);
}

public function getData()
{
$this->validate('amount', 'description');
$this->validate('amount', 'item_description');

$data = array();

// Merchant Details
$data['merchant_id'] = $this->getMerchantId();
$data['merchant_key'] = $this->getMerchantKey();
$data['return_url'] = $this->getReturnUrl();
$data['cancel_url'] = $this->getCancelUrl();
$data['notify_url'] = $this->getReturnUrl();
$data['notify_url'] = $this->getNotifyUrl();

// Payer Details (optional)
if ($this->getCard()) {
$data['name_first'] = $this->getCard()->getFirstName();
$data['name_last'] = $this->getCard()->getLastName();
$data['email_address'] = $this->getCard()->getEmail();
}

$data['m_payment_id'] = $this->getTransactionId();
// Transaction Details
$data['m_payment_id'] = $this->getMPaymentId(); // 100 char
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yes as above. Please use the Omnipay standard getTransactionId() instead of a custom method here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here again the user would have to know not to use payfast m_payment_id but to use omnipays TransactionId.

$data['amount'] = $this->getAmount();
$data['item_name'] = $this->getDescription();
$data['item_name'] = $this->getItemDescription(); // 100 char
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the existing getDescription() method here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to keep with payfast documentation. Not doing this would mean the user would have to call setDescription() instead of just passing the array of data as per payfast documentation.

$data['item_description'] = ""; // 255 char
$data['custom_int1'] = $this->getCustomInt1();
$data['custom_int2'] = $this->getCustomInt2();
$data['custom_int3'] = $this->getCustomInt3();
$data['custom_int4'] = $this->getCustomInt4();
$data['custom_int5'] = $this->getCustomInt5();
$data['custom_str1'] = $this->getCustomStr1(); // 255 char
$data['custom_str2'] = $this->getCustomStr2(); // 255 char
$data['custom_str3'] = $this->getCustomStr3(); // 255 char
$data['custom_str4'] = $this->getCustomStr4(); // 255 char
$data['custom_str5'] = $this->getCustomStr5(); // 255 char

// Transaction Options(optional)
$data['email_confirmation'] = $this->getEmailConfirmation();
$data['confirmation_address'] = $this->getConfirmationAddress(); // 100 char
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these fields for? We already have $card->getEmail() which you can probably use.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new fields are all new fields that the payfats API supports. Will have a look at the $card->variable and see where I can use it.


$data['signature'] = $this->generateSignature($data);

Expand All @@ -70,23 +239,34 @@ public function getData()

protected function generateSignature($data)
{
$fields = array();
$pfData = [];
foreach( $data as $key => $val ) {
$pfData[$key] = stripslashes( $val );
}

// specific order required by PayFast
foreach (array('merchant_id', 'merchant_key', 'return_url', 'cancel_url', 'notify_url',
'name_first', 'name_last', 'email_address', 'm_payment_id', 'amount', 'item_name',
'item_description', 'email_confirmation', 'confirmation_address') as $key) {
if (!empty($data[$key])) {
$fields[$key] = $data[$key];
$pfParamString = '';
foreach( $pfData as $key => $val ) {
if( $key != 'signature' ) {
$pfParamString .= $key .'='. urlencode( $val ) .'&';
}
}

return md5(http_build_query($fields));
$pfParamString = substr($pfParamString, 0, -1);
$pfTempParamString = $pfParamString;

$passPhrase = $this->getPassPhrase();
if( !empty( $passPhrase ) )
{
$pfTempParamString .= '&passphrase='.urlencode( $passPhrase );
}
$signature = md5( $pfTempParamString );

return md5( $pfTempParamString );
}

public function sendData($data)
{
return $this->response = new PurchaseResponse($this, $data, $this->getEndpoint().'/process');
return $this->response = new PurchaseResponse($this, $data, $this->getEndpoint() . '/process');
}

public function getEndpoint()
Expand Down
49 changes: 37 additions & 12 deletions tests/Message/PurchaseRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,50 @@ public function setUp()

public function testSignature()
{
$this->request->initialize(
array(
'amount' => '12.00',
'description' => 'Test Product',
'transactionId' => 123,
'merchantId' => 'foo',
'merchantKey' => 'bar',
'returnUrl' => 'https://www.example.com/return',
'cancelUrl' => 'https://www.example.com/cancel',
)
$data = array(
'amount' => '12.00',
'item_description' => 'Test Product',
'm_payment_id' => 123,
'merchant_id' => 'foo',
'merchant_key' => 'bar',
'return_url' => 'https://www.example.com/return',
'cancel_url' => 'https://www.example.com/cancel',
);

$this->request->initialize($data);

$data = $this->request->getData();
$this->assertSame('ab86df60906e97d3bfb362aff26fd9e6', $data['signature']);
$this->assertSame($this->generateSignature($data), $data['signature']);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this test removed? Also can you write some tests for the rest of the code please.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test function has no purpose after the changes made to generateSignature() method, the signature is produced from the specific order of the data and if the values are not empty. Will look at more tests.

protected function generateSignature($data)
{
// Strip any slashes in data
$pfData = [];
foreach( $data as $key => $val ) {
$pfData[$key] = stripslashes( $val );
}

// Dump the submitted variables and calculate security signature
$pfParamString = '';
foreach( $pfData as $key => $val ) {
if( $key != 'signature' ) {
$pfParamString .= $key .'='. urlencode( $val ) .'&';
}
}

// Remove the last '&' from the parameter string
$pfParamString = substr($pfParamString, 0, -1);
$pfTempParamString = $pfParamString;

$signature = md5( $pfTempParamString );

return md5( $pfTempParamString );
}

public function testPurchase()
{
$this->request->setAmount('12.00')->setDescription('Test Product');
$this->request->setAmount('12.00')->setItemDescription('Test Product');

$response = $this->request->send();

Expand Down