Skip to content

Commit e03dc4c

Browse files
committed
Add support for cancelling purchased numbers
This also updates the tests to use a 555 number so we don't accidentally interact with a real person. Purchasing a number can now be done by providing just a number (the same as cancelling a number). This is done by making an additional GET request
1 parent c283de3 commit e03dc4c

File tree

6 files changed

+133
-36
lines changed

6 files changed

+133
-36
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.idea
22
composer.lock
33
vendor
4+
logs

src/Numbers/Client.php

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -182,24 +182,18 @@ private function handleNumberSearchResult($response, $number)
182182
return $numbers;
183183
}
184184

185-
public function purchase($numberOrMsisdn, $country = null) {
186-
187-
if ($numberOrMsisdn instanceof Number) {
188-
$body = [
189-
'msisdn' => $numberOrMsisdn->getMsisdn(),
190-
'country' => $numberOrMsisdn->getCountry()
191-
];
192-
} else {
193-
if (is_null($country)) {
194-
throw new \InvalidArgumentException('Must provide a country when trying to purchase a number');
195-
}
196-
197-
$body = [
198-
'msisdn' => $numberOrMsisdn,
199-
'country' => $country
200-
];
185+
public function purchase($number) {
186+
// We cheat here and fetch a number using the API so that we have the country code which is required
187+
// to make a cancel request
188+
if (!$number instanceof Number) {
189+
$number = $this->get($number);
201190
}
202191

192+
$body = [
193+
'msisdn' => $number->getMsisdn(),
194+
'country' => $number->getCountry()
195+
];
196+
203197
$request = new Request(
204198
\Nexmo\Client::BASE_REST . '/number/buy',
205199
'POST',
@@ -225,6 +219,38 @@ public function purchase($numberOrMsisdn, $country = null) {
225219
}
226220
}
227221

222+
public function cancel($number) {
223+
// We cheat here and fetch a number using the API so that we have the country code which is required
224+
// to make a cancel request
225+
if (!$number instanceof Number) {
226+
$number = $this->get($number);
227+
}
228+
229+
$body = [
230+
'msisdn' => $number->getMsisdn(),
231+
'country' => $number->getCountry()
232+
];
233+
234+
$request = new Request(
235+
\Nexmo\Client::BASE_REST . '/number/cancel',
236+
'POST',
237+
'php://temp',
238+
[
239+
'Accept' => 'application/json',
240+
'Content-Type' => 'application/x-www-form-urlencoded'
241+
]
242+
);
243+
244+
$request->getBody()->write(http_build_query($body));
245+
$response = $this->client->send($request);
246+
247+
// Sadly we can't distinguish *why* purchasing fails, just that it
248+
// has failed.
249+
if('200' != $response->getStatusCode()){
250+
throw $this->getException($response);
251+
}
252+
}
253+
228254
protected function getException(ResponseInterface $response)
229255
{
230256
$body = json_decode($response->getBody()->getContents(), true);

test/Numbers/ClientTest.php

Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function testUpdateNumber($payload, $id, $expectedId, $lookup)
4141
{
4242
//based on the id provided, may need to look up the number first
4343
if($lookup){
44-
if(is_null($id) OR ('12404284163' == $id)){
44+
if(is_null($id) OR ('1415550100' == $id)){
4545
$first = $this->getResponse('single');
4646
} else {
4747
$first = $this->getResponse('single-update');
@@ -101,30 +101,30 @@ public function updateNumber()
101101
];
102102

103103
$rawId['country'] = 'US';
104-
$rawId['msisdn'] = '12404284163';
104+
$rawId['msisdn'] = '1415550100';
105105

106-
$number = new Number('12404284163');
106+
$number = new Number('1415550100');
107107
$number->setWebhook(Number::WEBHOOK_MESSAGE, 'https://example.com/new_message');
108108
$number->setWebhook(Number::WEBHOOK_VOICE_STATUS, 'https://example.com/new_status');
109109
$number->setVoiceDestination('https://example.com/new_voice');
110110

111-
$noLookup = new Number('12404284163', 'US');
111+
$noLookup = new Number('1415550100', 'US');
112112
$noLookup->setWebhook(Number::WEBHOOK_MESSAGE, 'https://example.com/new_message');
113113
$noLookup->setWebhook(Number::WEBHOOK_VOICE_STATUS, 'https://example.com/new_status');
114114
$noLookup->setVoiceDestination('https://example.com/new_voice');
115115

116-
$fresh = new Number('12404284163', 'US');
116+
$fresh = new Number('1415550100', 'US');
117117
$fresh->setWebhook(Number::WEBHOOK_MESSAGE, 'https://example.com/new_message');
118118
$fresh->setWebhook(Number::WEBHOOK_VOICE_STATUS, 'https://example.com/new_status');
119119
$fresh->setVoiceDestination('https://example.com/new_voice');
120120

121121
return [
122-
[$raw, '12404284163', '12404284163', true],
123-
[$rawId, null, '12404284163', false],
124-
[clone $number, null, '12404284163', true],
125-
[clone $number, '14845551212', '14845551212', true],
126-
[clone $noLookup, null, '12404284163', false],
127-
[clone $fresh, '12404284163', '12404284163', true],
122+
[$raw, '1415550100', '1415550100', true],
123+
[$rawId, null, '1415550100', false],
124+
[clone $number, null, '1415550100', true],
125+
[clone $number, '1415550100', '1415550100', true],
126+
[clone $noLookup, null, '1415550100', false],
127+
[clone $fresh, '1415550100', '1415550100', true],
128128
];
129129
}
130130

@@ -154,8 +154,8 @@ public function testGetNumber($payload, $id)
154154
public function numbers()
155155
{
156156
return [
157-
['12404284163', '12404284163'],
158-
[new Number('12404284163'), '12404284163'],
157+
['1415550100', '1415550100'],
158+
[new Number('1415550100'), '1415550100'],
159159
];
160160
}
161161

@@ -249,11 +249,20 @@ public function testPurchaseNumberWithNumberObject()
249249

250250
public function testPurchaseNumberWithNumberAndCountry()
251251
{
252+
// When providing a number string, the first thing that happens is a GET request to fetch number details
252253
$this->nexmoClient->send(Argument::that(function(RequestInterface $request){
253-
$this->assertEquals('/number/buy', $request->getUri()->getPath());
254-
$this->assertEquals('rest.nexmo.com', $request->getUri()->getHost());
255-
$this->assertEquals('POST', $request->getMethod());
256-
return true;
254+
return $request->getUri()->getPath() === '/account/numbers';
255+
}))->willReturn($this->getResponse('single'));
256+
257+
// Then we purchase the number
258+
$this->nexmoClient->send(Argument::that(function(RequestInterface $request){
259+
if ($request->getUri()->getPath() === '/number/buy') {
260+
$this->assertEquals('/number/buy', $request->getUri()->getPath());
261+
$this->assertEquals('rest.nexmo.com', $request->getUri()->getHost());
262+
$this->assertEquals('POST', $request->getMethod());
263+
return true;
264+
}
265+
return false;
257266
}))->willReturn($this->getResponse('post'));
258267
$this->numberClient->purchase('1415550100', 'US');
259268

@@ -276,7 +285,8 @@ public function testPurchaseNumberErrors($number, $country, $responseFile, $expe
276285
$this->expectException($expectedException);
277286
$this->expectExceptionMessage($expectedExceptionMessage);
278287

279-
$this->numberClient->purchase($number, $country);
288+
$num = new Number($number, $country);
289+
$this->numberClient->purchase($num);
280290
}
281291

282292
public function purchaseNumberErrorProvider()
@@ -290,6 +300,62 @@ public function purchaseNumberErrorProvider()
290300
return $r;
291301
}
292302

303+
public function testCancelNumberWithNumberObject()
304+
{
305+
$this->nexmoClient->send(Argument::that(function(RequestInterface $request){
306+
$this->assertEquals('/number/cancel', $request->getUri()->getPath());
307+
$this->assertEquals('rest.nexmo.com', $request->getUri()->getHost());
308+
$this->assertEquals('POST', $request->getMethod());
309+
return true;
310+
}))->willReturn($this->getResponse('cancel'));
311+
312+
$number = new Number('1415550100', 'US');
313+
$this->numberClient->cancel($number);
314+
315+
// There's nothing to assert here as we don't do anything with the response.
316+
// If there's no exception thrown, everything is fine!
317+
}
318+
319+
public function testCancelNumberWithNumberString()
320+
{
321+
322+
// When providing a number string, the first thing that happens is a GET request to fetch number details
323+
$this->nexmoClient->send(Argument::that(function(RequestInterface $request){
324+
return $request->getUri()->getPath() === '/account/numbers';
325+
}))->willReturn($this->getResponse('single'));
326+
327+
328+
// Then we get a POST request to cancel
329+
$this->nexmoClient->send(Argument::that(function(RequestInterface $request) {
330+
if ($request->getUri()->getPath() === '/number/cancel') {
331+
$this->assertEquals('rest.nexmo.com', $request->getUri()->getHost());
332+
$this->assertEquals('POST', $request->getMethod());
333+
return true;
334+
}
335+
return false;
336+
}))->willReturn($this->getResponse('cancel'));
337+
338+
$this->numberClient->cancel('1415550100');
339+
340+
// There's nothing to assert here as we don't do anything with the response.
341+
// If there's no exception thrown, everything is fine!
342+
}
343+
344+
public function testCancelNumberError()
345+
{
346+
$this->nexmoClient->send(Argument::that(function(RequestInterface $request){
347+
$this->assertEquals('/number/cancel', $request->getUri()->getPath());
348+
$this->assertEquals('rest.nexmo.com', $request->getUri()->getHost());
349+
$this->assertEquals('POST', $request->getMethod());
350+
return true;
351+
}))->willReturn($this->getResponse('method-failed', 420));
352+
353+
$this->expectException(Exception\Request::class);
354+
$this->expectExceptionMessage('method failed');
355+
356+
$num = new Number('1415550100', 'US');
357+
$this->numberClient->cancel($num);
358+
}
293359

294360
/**
295361
* Get the API response we'd expect for a call to the API.

test/Numbers/NumberTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function testHydrate()
4545
$this->number->jsonUnserialize($data['numbers'][0]);
4646

4747
$this->assertEquals('US', $this->number->getCountry());
48-
$this->assertEquals('12404284163', $this->number->getNumber());
48+
$this->assertEquals('1415550100', $this->number->getNumber());
4949
$this->assertEquals(Number::TYPE_MOBILE, $this->number->getType());
5050

5151
$this->assertEquals('http://example.com/message', $this->number->getWebhook(Number::WEBHOOK_MESSAGE));

test/Numbers/responses/cancel.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"error-code": "200",
3+
"error-code-label": "success"
4+
}

test/Numbers/responses/single.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"numbers": [
44
{
55
"country": "US",
6-
"msisdn": "12404284163",
6+
"msisdn": "1415550100",
77
"moHttpUrl": "http://example.com/message",
88
"type": "mobile-lvn",
99
"features": [

0 commit comments

Comments
 (0)