Skip to content

Commit 682abbf

Browse files
kevinpjonesclaude
andcommitted
BILL-5582: Add descriptor_code support to bank account verification
Stripe updated microdeposit verification from two amounts to a single 6-character descriptor code. This adds support for the new path while keeping the existing amounts path fully backwards compatible. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 556974b commit 682abbf

4 files changed

Lines changed: 202 additions & 20 deletions

File tree

lib/Model/BankAccount.php

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ class BankAccount implements ModelInterface, ArrayAccess, \JsonSerializable
7373
'date_created' => '\DateTime',
7474
'date_modified' => '\DateTime',
7575
'deleted' => 'bool',
76-
'object' => 'string'
76+
'object' => 'string',
77+
'microdeposit_type' => 'string'
7778
];
7879

7980
/**
@@ -97,7 +98,8 @@ class BankAccount implements ModelInterface, ArrayAccess, \JsonSerializable
9798
'date_created' => 'date-time',
9899
'date_modified' => 'date-time',
99100
'deleted' => null,
100-
'object' => null
101+
'object' => null,
102+
'microdeposit_type' => null
101103
];
102104

103105
/**
@@ -140,7 +142,8 @@ public static function openAPIFormats()
140142
'date_created' => 'date_created',
141143
'date_modified' => 'date_modified',
142144
'deleted' => 'deleted',
143-
'object' => 'object'
145+
'object' => 'object',
146+
'microdeposit_type' => 'microdeposit_type'
144147
];
145148

146149
/**
@@ -162,7 +165,8 @@ public static function openAPIFormats()
162165
'date_created' => 'setDateCreated',
163166
'date_modified' => 'setDateModified',
164167
'deleted' => 'setDeleted',
165-
'object' => 'setObject'
168+
'object' => 'setObject',
169+
'microdeposit_type' => 'setMicrodepositType'
166170
];
167171

168172
/**
@@ -184,7 +188,8 @@ public static function openAPIFormats()
184188
'date_created' => 'getDateCreated',
185189
'date_modified' => 'getDateModified',
186190
'deleted' => 'getDeleted',
187-
'object' => 'getObject'
191+
'object' => 'getObject',
192+
'microdeposit_type' => 'getMicrodepositType'
188193
];
189194

190195
/**
@@ -286,6 +291,7 @@ public function __construct(array $data = null)
286291
$this->container['date_modified'] = $data['date_modified'] ?? null;
287292
$this->container['deleted'] = $data['deleted'] ?? null;
288293
$this->container['object'] = $data['object'] ?? null;
294+
$this->container['microdeposit_type'] = $data['microdeposit_type'] ?? null;
289295
}
290296

291297
/**
@@ -836,6 +842,32 @@ public function setObject($object)
836842

837843
return $this;
838844
}
845+
846+
847+
/**
848+
* Gets microdeposit_type
849+
*
850+
* @return string|null
851+
*/
852+
public function getMicrodepositType()
853+
{
854+
return $this->container['microdeposit_type'];
855+
}
856+
857+
/**
858+
* Sets microdeposit_type
859+
*
860+
* @param string|null $microdeposit_type The type of microdeposit verification required. Present when verified is false; null once the account is verified. Use this to determine which field to submit to the verify endpoint: amounts or descriptor_code.
861+
*
862+
* @return self
863+
*/
864+
public function setMicrodepositType($microdeposit_type)
865+
{
866+
$this->container['microdeposit_type'] = $microdeposit_type;
867+
868+
return $this;
869+
}
870+
839871
/**
840872
* Returns true if offset exists. False otherwise.
841873
*

lib/Model/BankAccountVerify.php

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ class BankAccountVerify implements ModelInterface, ArrayAccess, \JsonSerializabl
6060
* @var string[]
6161
*/
6262
protected static $openAPITypes = [
63-
'amounts' => 'int[]'
63+
'amounts' => 'int[]',
64+
'descriptor_code' => 'string'
6465
];
6566

6667
/**
@@ -71,7 +72,8 @@ class BankAccountVerify implements ModelInterface, ArrayAccess, \JsonSerializabl
7172
* @psalm-var array<string, string|null>
7273
*/
7374
protected static $openAPIFormats = [
74-
'amounts' => null
75+
'amounts' => null,
76+
'descriptor_code' => null
7577
];
7678

7779
/**
@@ -101,7 +103,8 @@ public static function openAPIFormats()
101103
* @var string[]
102104
*/
103105
protected static $attributeMap = [
104-
'amounts' => 'amounts'
106+
'amounts' => 'amounts',
107+
'descriptor_code' => 'descriptor_code'
105108
];
106109

107110
/**
@@ -110,7 +113,8 @@ public static function openAPIFormats()
110113
* @var string[]
111114
*/
112115
protected static $setters = [
113-
'amounts' => 'setAmounts'
116+
'amounts' => 'setAmounts',
117+
'descriptor_code' => 'setDescriptorCode'
114118
];
115119

116120
/**
@@ -119,7 +123,8 @@ public static function openAPIFormats()
119123
* @var string[]
120124
*/
121125
protected static $getters = [
122-
'amounts' => 'getAmounts'
126+
'amounts' => 'getAmounts',
127+
'descriptor_code' => 'getDescriptorCode'
123128
];
124129

125130
/**
@@ -180,6 +185,7 @@ public function getModelName()
180185
public function __construct(array $data = null)
181186
{
182187
$this->container['amounts'] = $data['amounts'] ?? null;
188+
$this->container['descriptor_code'] = $data['descriptor_code'] ?? null;
183189
}
184190

185191
/**
@@ -192,19 +198,26 @@ public function listInvalidProperties()
192198
$invalidProperties = [];
193199

194200
if (!method_exists($this, 'getId') || (!empty($this->getId()) && strpos($this->getId(), "fakeId") === False)) {
195-
if ($this->container['amounts'] === null) {
196-
$invalidProperties[] = "'amounts' can't be null";
197-
}
198-
}
199-
if (!method_exists($this, 'getId') || (!empty($this->getId()) && strpos($this->getId(), "fakeId") === False)) {
200-
if ((count($this->container['amounts']) > 2)) {
201-
$invalidProperties[] = "invalid value for 'amounts', number of items must be less than or equal to 2.";
201+
$hasAmounts = $this->container['amounts'] !== null;
202+
$hasDescriptorCode = $this->container['descriptor_code'] !== null;
203+
204+
if (!$hasAmounts && !$hasDescriptorCode) {
205+
$invalidProperties[] = "one of 'amounts' or 'descriptor_code' must be provided";
202206
}
203207

204-
if ((count($this->container['amounts']) < 2)) {
205-
$invalidProperties[] = "invalid value for 'amounts', number of items must be greater than or equal to 2.";
208+
if ($hasAmounts && $hasDescriptorCode) {
209+
$invalidProperties[] = "only one of 'amounts' or 'descriptor_code' may be provided";
206210
}
207211

212+
if ($hasAmounts) {
213+
if ((count($this->container['amounts']) > 2)) {
214+
$invalidProperties[] = "invalid value for 'amounts', number of items must be less than or equal to 2.";
215+
}
216+
217+
if ((count($this->container['amounts']) < 2)) {
218+
$invalidProperties[] = "invalid value for 'amounts', number of items must be greater than or equal to 2.";
219+
}
220+
}
208221
}
209222
return $invalidProperties;
210223
}
@@ -253,14 +266,43 @@ public function setAmounts($amounts)
253266
$this->container['amounts'] = [];
254267
if ($amounts) {
255268
foreach ($amounts as $point) {
256-
269+
257270
$deserializedData = (int) $point;
258271
array_push($this->container['amounts'], $deserializedData);
259272
}
260273
}
261274

262275
return $this;
263276
}
277+
278+
/**
279+
* Gets descriptor_code
280+
*
281+
* @return string|null
282+
*/
283+
public function getDescriptorCode()
284+
{
285+
return $this->container['descriptor_code'];
286+
}
287+
288+
/**
289+
* Sets descriptor_code
290+
*
291+
* @param string|null $descriptor_code The 6-character code (beginning with SM) from the bank statement descriptor of the single $0.01 microdeposit. Required when microdeposit_type is descriptor_code.
292+
*
293+
* @return self
294+
*/
295+
public function setDescriptorCode($descriptor_code)
296+
{
297+
if (!method_exists($this, 'getId') || (!empty($this->getId()) && strpos($this->getId(), "fakeId") === False)) {
298+
if (!is_null($descriptor_code) && !preg_match("/^SM[a-zA-Z0-9]{4}$/", $descriptor_code)) {
299+
throw new \InvalidArgumentException('invalid value for $descriptor_code when calling BankAccountVerify., must conform to the pattern /^SM[a-zA-Z0-9]{4}$/.');
300+
}
301+
}
302+
$this->container['descriptor_code'] = $descriptor_code;
303+
304+
return $this;
305+
}
264306
/**
265307
* Returns true if offset exists. False otherwise.
266308
*

test/Integration/BankAccountsApiSpecTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,4 +360,31 @@ public function testDelete404()
360360
$this->expectExceptionMessageMatches("/bank account not found/");
361361
$badDeletion = self::$bankApi->delete("bank_NONEXISTENT");
362362
}
363+
364+
public function testVerifyWithDescriptorCode200()
365+
{
366+
try {
367+
$createdBankAccount = self::$bankApi->create(self::$writableBankAcc);
368+
$descriptorVerify = new BankAccountVerify();
369+
$descriptorVerify->setDescriptorCode("SM11AA");
370+
$verifiedBankAccount = self::$bankApi->verify($createdBankAccount->getId(), $descriptorVerify);
371+
$this->assertMatchesRegularExpression("/bank_/", $verifiedBankAccount->getId());
372+
array_push($this->idsForCleanup, $createdBankAccount->getId());
373+
} catch (\Exception $e) {
374+
throw new \Exception($e->getMessage());
375+
}
376+
}
377+
378+
public function testBankAccountHasMicrodepositType()
379+
{
380+
try {
381+
$createdBankAccount = self::$bankApi->create(self::$writableBankAcc);
382+
$retrievedBankAccount = self::$bankApi->get($createdBankAccount->getId());
383+
$this->assertNotNull($retrievedBankAccount->getMicrodepositType());
384+
$this->assertContains($retrievedBankAccount->getMicrodepositType(), ["amounts", "descriptor_code"]);
385+
array_push($this->idsForCleanup, $createdBankAccount->getId());
386+
} catch (\Exception $e) {
387+
throw new \Exception($e->getMessage());
388+
}
389+
}
363390
}

test/Unit/BankAccountsApiUnitTest.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,4 +836,85 @@ public function testVerifyFailStatusCode()
836836
echo 'Caught exception: ', $instantiationError->getMessage(), "\n";
837837
}
838838
}
839+
840+
/**
841+
* @group unit
842+
* @group bankAccounts
843+
*/
844+
public function testVerifyWithDescriptorCode()
845+
{
846+
$guzzleMock = new MockHandler();
847+
$handlerStack = HandlerStack::create($guzzleMock);
848+
$client = new Client(['handler' => $handlerStack]);
849+
$config = new Configuration();
850+
$config->setApiKey('basic', 'Totally Fake Key');
851+
$bankAccountsApi = new BankAccountsApi($config, $client);
852+
853+
$guzzleMock->append(new Response(200, [], self::$mockBankAccountResponse));
854+
try {
855+
$verify = new BankAccountVerify(array("descriptor_code" => "SM11AA"));
856+
$happyPath = $bankAccountsApi->verify(self::$mockBankId, $verify);
857+
$this->assertEquals($happyPath->getId(), self::$mockBankId);
858+
} catch (Exception $retrieveError) {
859+
echo 'Caught exception: ', $retrieveError->getMessage(), "\n";
860+
}
861+
}
862+
863+
/**
864+
* @group unit
865+
* @group bankAccounts
866+
*/
867+
public function testVerifyFailBothAmountsAndDescriptorCode()
868+
{
869+
$verify = new BankAccountVerify(array("amounts" => [1, 2], "descriptor_code" => "SM11AA"));
870+
$invalidProps = $verify->listInvalidProperties();
871+
$this->assertNotEmpty($invalidProps);
872+
$this->assertStringContainsString("only one of", $invalidProps[0]);
873+
$this->assertFalse($verify->valid());
874+
}
875+
876+
/**
877+
* @group unit
878+
* @group bankAccounts
879+
*/
880+
public function testVerifyFailNeitherAmountsNorDescriptorCode()
881+
{
882+
$verify = new BankAccountVerify();
883+
$invalidProps = $verify->listInvalidProperties();
884+
$this->assertNotEmpty($invalidProps);
885+
$this->assertStringContainsString("one of 'amounts' or 'descriptor_code' must be provided", $invalidProps[0]);
886+
}
887+
888+
/**
889+
* @group unit
890+
* @group bankAccounts
891+
*/
892+
public function testVerifyFailInvalidDescriptorCodePattern()
893+
{
894+
try {
895+
$this->expectException(\InvalidArgumentException::class);
896+
$verify = new BankAccountVerify();
897+
$verify->setDescriptorCode("INVALID");
898+
} catch (Exception $e) {
899+
echo 'Caught exception: ', $e->getMessage(), "\n";
900+
}
901+
}
902+
903+
/**
904+
* @group unit
905+
* @group bankAccounts
906+
*/
907+
public function testBankAccountHasMicrodepositType()
908+
{
909+
$account = new BankAccount();
910+
$account->setId(self::$mockBankId);
911+
$account->setMicrodepositType("amounts");
912+
$this->assertEquals("amounts", $account->getMicrodepositType());
913+
914+
$account->setMicrodepositType("descriptor_code");
915+
$this->assertEquals("descriptor_code", $account->getMicrodepositType());
916+
917+
$account->setMicrodepositType(null);
918+
$this->assertNull($account->getMicrodepositType());
919+
}
839920
}

0 commit comments

Comments
 (0)