Skip to content

Commit ca992b2

Browse files
committed
Merge pull request #53 from dignat/master
Filter invalid characters from basket item names, handle multiple discounts and no discounts.
2 parents 4208d23 + cf4795f commit ca992b2

File tree

2 files changed

+119
-7
lines changed

2 files changed

+119
-7
lines changed

src/Message/AbstractRequest.php

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,46 @@ protected function createResponse($data)
144144
return $this->response = new Response($this, $data);
145145
}
146146

147+
/**
148+
* Filters out any characters that SagePay does not support from the item name.
149+
*
150+
* Believe it or not, SagePay actually have separate rules for allowed characters
151+
* for item names and discount names, hence the need for two separate methods.
152+
*
153+
* @param string $name
154+
*
155+
* @return string
156+
*/
157+
protected function filterItemName($name)
158+
{
159+
$standardChars = "0-9a-zA-Z";
160+
$allowedSpecialChars = " +'/\\&:,.-{}";
161+
$pattern = '`[^'.$standardChars.preg_quote($allowedSpecialChars, '/').']`';
162+
$name = trim(substr(preg_replace($pattern, '', $name), 0, 100));
163+
164+
return $name;
165+
}
166+
167+
/**
168+
* Filters out any characters that SagePay does not support from the discount name.
169+
*
170+
* Believe it or not, SagePay actually have separate rules for allowed characters
171+
* for item names and discount names, hence the need for two separate methods.
172+
*
173+
* @param string $name
174+
*
175+
* @return string
176+
*/
177+
protected function filterDiscountName($name)
178+
{
179+
$standardChars = "0-9a-zA-Z";
180+
$allowedSpecialChars = " +'/\\:,.-{};_@()^\"~[]$=!#?|";
181+
$pattern = '`[^'.$standardChars.preg_quote($allowedSpecialChars, '/').']`';
182+
$name = trim(substr(preg_replace($pattern, '', $name), 0, 100));
183+
184+
return $name;
185+
}
186+
147187
/**
148188
* Get an XML representation of the current cart items
149189
*
@@ -160,27 +200,33 @@ protected function getItemData()
160200
}
161201

162202
$xml = new \SimpleXMLElement('<basket/>');
203+
$cartHasDiscounts = false;
163204

164205
foreach ($items as $basketItem) {
165206
if ($basketItem->getPrice() < 0) {
166-
$discounts = $xml->addChild('discounts');
167-
$discount = $discounts->addChild('discount');
168-
$discount->addChild('fixed', $basketItem->getPrice() * -1);
169-
$discount->addChild('description', $basketItem->getName());
207+
$cartHasDiscounts = true;
170208
} else {
171209
$total = ($basketItem->getQuantity() * $basketItem->getPrice());
172210
$item = $xml->addChild('item');
173-
$item->addChild('description', $basketItem->getName());
211+
$item->description = $this->filterItemName($basketItem->getName());
174212
$item->addChild('quantity', $basketItem->getQuantity());
175213
$item->addChild('unitNetAmount', $basketItem->getPrice());
176214
$item->addChild('unitTaxAmount', '0.00');
177215
$item->addChild('unitGrossAmount', $basketItem->getPrice());
178216
$item->addChild('totalGrossAmount', $total);
179217
}
180218
}
181-
219+
if ($cartHasDiscounts) {
220+
$discounts = $xml->addChild('discounts');
221+
foreach ($items as $discountItem) {
222+
if ($discountItem->getPrice() < 0) {
223+
$discount = $discounts->addChild('discount');
224+
$discount->addChild('fixed', ($discountItem->getPrice() * $discountItem->getQuantity()) * -1);
225+
$discount->description = $this->filterDiscountName($discountItem->getName());
226+
}
227+
}
228+
}
182229
$xmlString = $xml->asXML();
183-
184230
if ($xmlString) {
185231
$result = $xmlString;
186232
}

tests/Message/DirectAuthorizeRequestTest.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
class DirectAuthorizeRequestTest extends TestCase
88
{
9+
/**
10+
* @var \Omnipay\Common\Message\AbstractRequest $request
11+
*/
12+
protected $request;
13+
914
public function setUp()
1015
{
1116
parent::setUp();
@@ -60,6 +65,7 @@ public function testNoBasket()
6065

6166
// Then with a basket containing no items.
6267
$items = new \Omnipay\Common\ItemBag(array());
68+
$this->request->setItems($items);
6369
$data = $this->request->getData();
6470
$this->assertArrayNotHasKey('BasketXML', $data);
6571
}
@@ -182,4 +188,64 @@ public function testGetDataNullBillingAddress2()
182188
// converted to an empty string. I'm not clear how that would be
183189
// tested.
184190
}
191+
192+
public function testBasketWithNoDiscount()
193+
{
194+
$items = new \Omnipay\Common\ItemBag(array(
195+
new \Omnipay\Common\Item(array(
196+
'name' => 'Name',
197+
'description' => 'Description',
198+
'quantity' => 1,
199+
'price' => 1.23,
200+
))
201+
));
202+
203+
$this->request->setItems($items);
204+
$data = $this->request->getData();
205+
// The element does exist, and must contain the basket XML, with optional XML header and
206+
// trailing newlines.
207+
$this->assertArrayHasKey('BasketXML', $data);
208+
$this->assertNotContains('<discount>', $data['BasketXML']);
209+
}
210+
211+
public function testMixedBasketWithSpecialChars()
212+
{
213+
$items = new \Omnipay\Common\ItemBag(array(
214+
new \Omnipay\Common\Item(array(
215+
'name' => "Denisé's Odd & Wierd £name? #12345678901234567890123456789012345678901234567890123456789012345678901234567890",
216+
'description' => 'Description',
217+
'quantity' => 2,
218+
'price' => 4.23,
219+
)),
220+
array(
221+
'name' => "Denisé's \"Odd\" & Wierd £discount? #",
222+
'description' => 'My Offer',
223+
'quantity' => 2,
224+
'price' => -0.10,
225+
),
226+
array(
227+
// 101 character name
228+
'name' => '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901',
229+
'description' => 'My 2nd Offer',
230+
'quantity' => 1,
231+
'price' => -1.60,
232+
)
233+
));
234+
235+
// Names/descriptions should be max 100 characters in length, once invalid characters have been removed.
236+
$expected = '<basket><item>'
237+
. '<description>Denis\'s Odd &amp; Wierd name 123456789012345678901234567890123456789012345678901234567890123456789012345</description><quantity>2</quantity>'
238+
. '<unitNetAmount>4.23</unitNetAmount><unitTaxAmount>0.00</unitTaxAmount>'
239+
. '<unitGrossAmount>4.23</unitGrossAmount><totalGrossAmount>8.46</totalGrossAmount>'
240+
. '</item><discounts>'
241+
. '<discount><fixed>0.2</fixed><description>Denis\'s "Odd" Wierd discount? #</description></discount>'
242+
. '<discount><fixed>1.6</fixed><description>1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890</description></discount>'
243+
. '</discounts></basket>';
244+
245+
$this->request->setItems($items);
246+
$data = $this->request->getData();
247+
248+
$this->assertArrayHasKey('BasketXML', $data);
249+
$this->assertContains($expected, $data['BasketXML'], 'Basket XML does not match the expected output');
250+
}
185251
}

0 commit comments

Comments
 (0)