Skip to content

Commit b546d24

Browse files
committed
Merge pull request #87 from bencromwell/feature/shipping-costs-callback
Add support for Instant Update API Callback
2 parents dc788fc + 7e2e440 commit b546d24

File tree

3 files changed

+324
-0
lines changed

3 files changed

+324
-0
lines changed

src/Message/ExpressAuthorizeRequest.php

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,86 @@
22

33
namespace Omnipay\PayPal\Message;
44

5+
use Omnipay\Common\Exception\InvalidRequestException;
6+
use Omnipay\PayPal\Support\InstantUpdateApi\ShippingOption;
7+
58
/**
69
* PayPal Express Authorize Request
710
*/
811
class ExpressAuthorizeRequest extends AbstractRequest
912
{
13+
14+
const DEFAULT_CALLBACK_TIMEOUT = 5;
15+
16+
public function setCallback($callback)
17+
{
18+
return $this->setParameter('callback', $callback);
19+
}
20+
21+
public function getCallback()
22+
{
23+
return $this->getParameter('callback');
24+
}
25+
26+
public function setCallbackTimeout($callbackTimeout)
27+
{
28+
return $this->setParameter('callbackTimeout', $callbackTimeout);
29+
}
30+
31+
public function getCallbackTimeout()
32+
{
33+
return $this->getParameter('callbackTimeout');
34+
}
35+
36+
/**
37+
* @param ShippingOption[] $data
38+
*/
39+
public function setShippingOptions($data)
40+
{
41+
$this->setParameter('shippingOptions', $data);
42+
}
43+
44+
/**
45+
* @return ShippingOption[]
46+
*/
47+
public function getShippingOptions()
48+
{
49+
return $this->getParameter('shippingOptions');
50+
}
51+
52+
protected function validateCallback()
53+
{
54+
$callback = $this->getCallback();
55+
56+
if (!empty($callback)) {
57+
$shippingOptions = $this->getShippingOptions();
58+
59+
if (empty($shippingOptions)) {
60+
throw new InvalidRequestException(
61+
'When setting a callback for the Instant Update API you must set shipping options'
62+
);
63+
} else {
64+
$hasDefault = false;
65+
foreach ($shippingOptions as $shippingOption) {
66+
if ($shippingOption->isDefault()) {
67+
$hasDefault = true;
68+
continue;
69+
}
70+
}
71+
72+
if (!$hasDefault) {
73+
throw new InvalidRequestException(
74+
'One of the supplied shipping options must be set as default'
75+
);
76+
}
77+
}
78+
}
79+
}
80+
1081
public function getData()
1182
{
1283
$this->validate('amount', 'returnUrl', 'cancelUrl');
84+
$this->validateCallback();
1385

1486
$data = $this->getBaseData();
1587
$data['METHOD'] = 'SetExpressCheckout';
@@ -34,6 +106,31 @@ public function getData()
34106
$data['LOCALECODE'] = $this->getLocaleCode();
35107
$data['CUSTOMERSERVICENUMBER'] = $this->getCustomerServiceNumber();
36108

109+
$callback = $this->getCallback();
110+
111+
if (!empty($callback)) {
112+
$data['CALLBACK'] = $callback;
113+
// callback timeout MUST be included and > 0
114+
$timeout = $this->getCallbackTimeout();
115+
116+
$data['CALLBACKTIMEOUT'] = $timeout > 0 ? $timeout : self::DEFAULT_CALLBACK_TIMEOUT;
117+
118+
// if you're using a callback you MUST set shipping option(s)
119+
$shippingOptions = $this->getShippingOptions();
120+
121+
if (!empty($shippingOptions)) {
122+
foreach ($shippingOptions as $index => $shipping) {
123+
$data['L_SHIPPINGOPTIONNAME' . $index] = $shipping->getName();
124+
$data['L_SHIPPINGOPTIONAMOUNT' . $index] = number_format($shipping->getAmount(), 2);
125+
$data['L_SHIPPINGOPTIONISDEFAULT' . $index] = $shipping->isDefault() ? '1' : '0';
126+
127+
if ($shipping->hasLabel()) {
128+
$data['L_SHIPPINGOPTIONLABEL' . $index] = $shipping->getLabel();
129+
}
130+
}
131+
}
132+
}
133+
37134
$data['MAXAMT'] = $this->getMaxAmount();
38135
$data['PAYMENTREQUEST_0_TAXAMT'] = $this->getTaxAmount();
39136
$data['PAYMENTREQUEST_0_SHIPPINGAMT'] = $this->getShippingAmount();
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace Omnipay\PayPal\Support\InstantUpdateApi;
4+
5+
class ShippingOption
6+
{
7+
/** @var string */
8+
private $name;
9+
10+
/** @var float */
11+
private $amount;
12+
13+
/** @var bool */
14+
private $isDefault;
15+
16+
/** @var string */
17+
private $label;
18+
19+
/**
20+
* @param string $name L_SHIPPINGOPTIONNAME0
21+
* @param float $amount L_SHIPPINGOPTIONAMOUNT0
22+
* @param bool $isDefault L_SHIPPINGOPTIONISDEFAULT0
23+
* @param string $label L_SHIPPINGOPTIONLABEL0
24+
*/
25+
public function __construct($name, $amount, $isDefault = false, $label = null)
26+
{
27+
$this->name = $name;
28+
$this->amount = $amount;
29+
$this->isDefault = $isDefault;
30+
$this->label = $label;
31+
}
32+
33+
/**
34+
* @return bool
35+
*/
36+
public function hasLabel()
37+
{
38+
return !is_null($this->label);
39+
}
40+
41+
/**
42+
* @return string
43+
*/
44+
public function getName()
45+
{
46+
return $this->name;
47+
}
48+
49+
/**
50+
* @return float
51+
*/
52+
public function getAmount()
53+
{
54+
return $this->amount;
55+
}
56+
57+
/**
58+
* @return boolean
59+
*/
60+
public function isDefault()
61+
{
62+
return $this->isDefault;
63+
}
64+
65+
/**
66+
* @return string
67+
*/
68+
public function getLabel()
69+
{
70+
return $this->label;
71+
}
72+
}

tests/Message/ExpressAuthorizeRequestTest.php

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Omnipay\PayPal\Message;
44

55
use Omnipay\Common\CreditCard;
6+
use Omnipay\PayPal\Support\InstantUpdateApi\ShippingOption;
67
use Omnipay\Tests\TestCase;
78

89
class ExpressAuthorizeRequestTest extends TestCase
@@ -215,4 +216,158 @@ public function testMaxAmount()
215216

216217
$this->assertSame(321.54, $data['MAXAMT']);
217218
}
219+
220+
public function testDataWithCallback()
221+
{
222+
$baseData = array(
223+
'amount' => '10.00',
224+
'currency' => 'AUD',
225+
'transactionId' => '111',
226+
'description' => 'Order Description',
227+
'returnUrl' => 'https://www.example.com/return',
228+
'cancelUrl' => 'https://www.example.com/cancel',
229+
'subject' => '[email protected]',
230+
'headerImageUrl' => 'https://www.example.com/header.jpg',
231+
'allowNote' => 0,
232+
'addressOverride' => 0,
233+
'brandName' => 'Dunder Mifflin Paper Company, Incy.',
234+
);
235+
236+
$shippingOptions = array(
237+
new ShippingOption('First Class', 1.20, true, '1-2 days'),
238+
new ShippingOption('Second Class', 0.70, false, '3-5 days'),
239+
new ShippingOption('International', 3.50),
240+
);
241+
242+
// with a default callback timeout
243+
$this->request->initialize(array_merge($baseData, array(
244+
'callback' => 'https://www.example.com/calculate-shipping',
245+
'shippingOptions' => $shippingOptions,
246+
)));
247+
248+
$data = $this->request->getData();
249+
$this->assertSame('https://www.example.com/calculate-shipping', $data['CALLBACK']);
250+
$this->assertSame(ExpressAuthorizeRequest::DEFAULT_CALLBACK_TIMEOUT, $data['CALLBACKTIMEOUT']);
251+
252+
$this->assertSame('First Class', $data['L_SHIPPINGOPTIONNAME0']);
253+
$this->assertSame('1.20', $data['L_SHIPPINGOPTIONAMOUNT0']);
254+
$this->assertSame('1', $data['L_SHIPPINGOPTIONISDEFAULT0']);
255+
$this->assertSame('1-2 days', $data['L_SHIPPINGOPTIONLABEL0']);
256+
257+
$this->assertSame('Second Class', $data['L_SHIPPINGOPTIONNAME1']);
258+
$this->assertSame('0.70', $data['L_SHIPPINGOPTIONAMOUNT1']);
259+
$this->assertSame('0', $data['L_SHIPPINGOPTIONISDEFAULT1']);
260+
$this->assertSame('3-5 days', $data['L_SHIPPINGOPTIONLABEL1']);
261+
262+
$this->assertSame('International', $data['L_SHIPPINGOPTIONNAME2']);
263+
$this->assertSame('3.50', $data['L_SHIPPINGOPTIONAMOUNT2']);
264+
$this->assertSame('0', $data['L_SHIPPINGOPTIONISDEFAULT2']);
265+
266+
// with a defined callback timeout
267+
$this->request->initialize(array_merge($baseData, array(
268+
'callback' => 'https://www.example.com/calculate-shipping',
269+
'callbackTimeout' => 10,
270+
'shippingOptions' => $shippingOptions,
271+
)));
272+
273+
$data = $this->request->getData();
274+
$this->assertSame('https://www.example.com/calculate-shipping', $data['CALLBACK']);
275+
$this->assertSame(10, $data['CALLBACKTIMEOUT']);
276+
}
277+
278+
public function testDataWithCallbackAndNoDefaultShippingOption()
279+
{
280+
$baseData = array(
281+
'amount' => '10.00',
282+
'currency' => 'AUD',
283+
'transactionId' => '111',
284+
'description' => 'Order Description',
285+
'returnUrl' => 'https://www.example.com/return',
286+
'cancelUrl' => 'https://www.example.com/cancel',
287+
'subject' => '[email protected]',
288+
'headerImageUrl' => 'https://www.example.com/header.jpg',
289+
'allowNote' => 0,
290+
'addressOverride' => 0,
291+
'brandName' => 'Dunder Mifflin Paper Company, Incy.',
292+
);
293+
294+
$shippingOptions = array(
295+
new ShippingOption('First Class', 1.20, false, '1-2 days'),
296+
new ShippingOption('Second Class', 0.70, false, '3-5 days'),
297+
new ShippingOption('International', 3.50),
298+
);
299+
300+
// with a default callback timeout
301+
$this->request->initialize(array_merge($baseData, array(
302+
'callback' => 'https://www.example.com/calculate-shipping',
303+
'shippingOptions' => $shippingOptions,
304+
)));
305+
306+
$this->setExpectedException(
307+
'\Omnipay\Common\Exception\InvalidRequestException',
308+
'One of the supplied shipping options must be set as default'
309+
);
310+
311+
$this->request->getData();
312+
}
313+
314+
public function testNoAmount()
315+
{
316+
$baseData = array(// nothing here - should cause a certain exception
317+
);
318+
319+
$this->request->initialize($baseData);
320+
321+
$this->setExpectedException(
322+
'\Omnipay\Common\Exception\InvalidRequestException',
323+
'The amount parameter is required'
324+
);
325+
326+
$this->request->getData();
327+
}
328+
329+
public function testAmountButNoReturnUrl()
330+
{
331+
$baseData = array(
332+
'amount' => 10.00,
333+
);
334+
335+
$this->request->initialize($baseData);
336+
337+
$this->setExpectedException(
338+
'\Omnipay\Common\Exception\InvalidRequestException',
339+
'The returnUrl parameter is required'
340+
);
341+
342+
$this->request->getData();
343+
}
344+
345+
public function testBadCallbackConfiguration()
346+
{
347+
$baseData = array(
348+
'amount' => '10.00',
349+
'currency' => 'AUD',
350+
'transactionId' => '111',
351+
'description' => 'Order Description',
352+
'returnUrl' => 'https://www.example.com/return',
353+
'cancelUrl' => 'https://www.example.com/cancel',
354+
'subject' => '[email protected]',
355+
'headerImageUrl' => 'https://www.example.com/header.jpg',
356+
'allowNote' => 0,
357+
'addressOverride' => 0,
358+
'brandName' => 'Dunder Mifflin Paper Company, Incy.',
359+
);
360+
361+
$this->request->initialize(array_merge($baseData, array(
362+
'callback' => 'https://www.example.com/calculate-shipping',
363+
)));
364+
365+
// from the docblock on this exception -
366+
// Thrown when a request is invalid or missing required fields.
367+
// callback has been set but no shipping options so expect one of these:
368+
$this->setExpectedException('\Omnipay\Common\Exception\InvalidRequestException');
369+
370+
$this->request->getData();
371+
}
372+
218373
}

0 commit comments

Comments
 (0)