Skip to content

Commit

Permalink
Merge pull request #13 from thebiggive/CLA-25-fix-agent-element
Browse files Browse the repository at this point in the history
CLA-25 – set up Agents' claims so payment is directly to charities
  • Loading branch information
Noel Light-Hilary authored Apr 19, 2022
2 parents 52c761f + 1d69489 commit b932c2e
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 86 deletions.
147 changes: 77 additions & 70 deletions src/GiftAid.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ class GiftAid extends GovTalk
private string $vendorId = '';

private static string $singleClaimMessageClass = 'HMRC-CHAR-CLM';
private static string $multiClaimMessageClass = 'HMRC-CHAR-CLM-MULTI';

/**
* URI for product submitting the claim
Expand Down Expand Up @@ -467,6 +466,10 @@ public function getGasdsAdjustment()
* @param string $company The agent company's name.
* @param array $address The agent company's address in the format specified above.
* @param ?array $contact The agent company's key contact (optional, may be skipped with a null value).
* If `['name']['surname']` is empty or not set, the whole name will be skipepd.
* This is done to enable just 'telephone' to be provided for claims where an
* `AgtOrNom` element is to be set up with an organisation name and the required
* phone number, but not other personal Agent information.
* @param ?string $reference An identifier for the agent's own reference (optional).
* @return bool Whether company format was as expected & agent data was set.
*/
Expand Down Expand Up @@ -522,8 +525,8 @@ public function setAgentDetails(
* 'aggregation' => (?string) Description of aggregated donations up to 35
* characters, if applicable
* 'amount' => (float) In whole pounds GBP
* 'org_name' => (?string) Required for Agent multi-charity claims. Ignored for others.
* 'org_hmrc_ref' => (?string) Required for Agent multi-charity claims. Ignored for others.
* 'org_name' => (?string) Required for Agent/Nominee [not Collection Agent] claims. Ignored for others.
* 'org_hmrc_ref' => (?string) Required for Agent/Nominee [not Collection Agent] claims. Ignored for others.
* ]
* @return string
*/
Expand All @@ -539,10 +542,10 @@ private function buildClaimXml(array $donations): string
$earliestDate = strtotime(date('Y-m-d'));

foreach ($donations as $index => $d) {
if ($this->isAgentMultiClaim()) {
if ($this->isAgentClaim()) {
if (empty($d['org_hmrc_ref'])) {
$this->logger->warning(sprintf(
'Skipping donation index %d (%s %s) with no org ref in agent multi mode',
'Skipping donation index %d (%s %s) with no org ref in agent mode',
$index,
$d['first_name'],
$d['last_name'],
Expand Down Expand Up @@ -653,7 +656,7 @@ public function giftAidSubmit($donor_data)
}

// It seems like only single org / direct claims use/need the Authorised Official.
if (!$this->isAgentMultiClaim() && $this->getAuthorisedOfficial() === null) {
if (!$this->isAgentClaim() && $this->getAuthorisedOfficial() === null) {
$this->logger->error('Cannot proceed without authorisedOfficial');
return false;
}
Expand All @@ -664,7 +667,7 @@ public function giftAidSubmit($donor_data)
$cOrganisation = 'IR';
$sDefaultCurrency = 'GBP'; // currently HMRC only allows GBP
$sIRmark = 'IRmark+Token';
$sSender = $this->isAgentMultiClaim() ? 'Agent' : 'Individual';
$sSender = $this->isAgentClaim() ? 'Agent' : 'Individual';

// Set the message envelope
$this->setMessageClass($this->getMessageClass());
Expand Down Expand Up @@ -699,7 +702,7 @@ public function giftAidSubmit($donor_data)
$package->endElement(); # Keys
$package->writeElement('PeriodEnd', $dReturnPeriod);

if ($this->isAgentMultiClaim()) {
if ($this->isAgentClaim()) {
$package->startElement('Agent');

$package->writeElement('Company', $this->agentDetails['company']);
Expand All @@ -717,7 +720,7 @@ public function giftAidSubmit($donor_data)
$package->writeElement('Country', $this->agentDetails['address']['country']);
$package->endElement(); // Address

if (isset($this->agentDetails['contact'])) {
if (isset($this->agentDetails['contact']) && !empty($this->agentDetails['contact']['name']['surname'])) {
$package->startElement('Contact');

$package->startElement('Name');
Expand Down Expand Up @@ -760,12 +763,26 @@ public function giftAidSubmit($donor_data)

$package->startElement('R68');

if ($this->isAgentMultiClaim()) {
$package->startElement('CollAgent');
$package->writeElement('AgentNo', $this->agentDetails['number']);
if ($this->isAgentClaim()) {
$package->startElement('AgtOrNom');
$package->writeElement('OrgName', $this->agentDetails['company']);
$package->writeElement('RefNo', $this->agentDetails['number']);
$claimNo = $this->agentDetails['reference'] ?? uniqid();
$package->writeElement('ClaimNo', $claimNo);
$package->endElement(); // CollAgent

// Note we never set `PayToAoN`. => HMRC always pay to the charity direct.

$package->startElement('AoNID');
if (empty($this->agentDetails['address']['postcode'])) {
$package->writeElement('Overseas', 'yes');
} else {
$package->writeElement('Postcode', $this->agentDetails['address']['postcode']);
}
$package->endElement(); // AoNID

$package->writeElement('Phone', $this->agentDetails['contact']['telephone']);

$package->endElement(); // AgtOrNom
} else {
$package->startElement('AuthOfficial');
$package->startElement('OffName');
Expand Down Expand Up @@ -978,7 +995,7 @@ protected function packageDigest($package)
return $package;
}

protected function isAgentMultiClaim(): bool
protected function isAgentClaim(): bool
{
return !empty($this->agentDetails);
}
Expand All @@ -988,22 +1005,20 @@ protected function isAgentMultiClaim(): bool
*/
protected function getMessageClass(): string
{
return $this->isAgentMultiClaim() ? static::$multiClaimMessageClass : static::$singleClaimMessageClass;
return static::$singleClaimMessageClass;
}

/**
* @link https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/735545/Charities-OnlineValidsV1.3.pdf
*/
protected function getCharIdKey(): string
{
return $this->isAgentMultiClaim() ? 'AGENTCHARID' : 'CHARID';
return 'CHARID';
}

protected function getCharIdValue(): string
{
return $this->isAgentMultiClaim()
? $this->agentDetails['number']
: $this->getClaimingOrganisation()->getHmrcRef();
return $this->getClaimingOrganisation()->getHmrcRef();
}

protected function writeClaimStartData(XMLWriter $package, ClaimingOrganisation $org): void
Expand All @@ -1012,21 +1027,17 @@ protected function writeClaimStartData(XMLWriter $package, ClaimingOrganisation
$package->writeElement('OrgName', $org->getName());
$package->writeElement('HMRCref', $org->getHmrcRef());

// LTS response code 7032: "Regulator details must not be present if
// Collecting Agent details are present"
if (!$this->isAgentMultiClaim()) {
$package->startElement('Regulator');
$package->startElement('Regulator');

if ($org->getRegulator() === null) {
$package->writeElement('NoReg', 'yes');
} elseif ($org->hasStandardRegulator()) {
$package->writeElement('RegName', $org->getRegulator());
} else {
$package->writeElement('OtherReg', $org->getRegulator());
}
$package->writeElement('RegNo', $org->getRegNo());
$package->endElement(); # Regulator
if ($org->getRegulator() === null) {
$package->writeElement('NoReg', 'yes');
} elseif ($org->hasStandardRegulator()) {
$package->writeElement('RegName', $org->getRegulator());
} else {
$package->writeElement('OtherReg', $org->getRegulator());
}
$package->writeElement('RegNo', $org->getRegNo());
$package->endElement(); # Regulator

$package->startElement('Repayment');
}
Expand All @@ -1040,49 +1051,45 @@ protected function writeClaimEndData(XMLWriter $package, $earliestDate): void
}
$package->endElement(); # Repayment

// LTS response code 7044: "A submission from a Collecting Agent must not include
// details of Gift Aid Small Donations Schemes"
if (!$this->isAgentMultiClaim()) {
$package->startElement('GASDS');
$package->writeElement(
'ConnectedCharities',
$this->getClaimingOrganisation()->getHasConnectedCharities() ? 'yes' : 'no'
);
foreach ($this->getClaimingOrganisation()->getConnectedCharities() as $cc) {
$package->startElement('Charity');
$package->writeElement('Name', $cc->getName());
$package->writeElement('HMRCref', $cc->getHmrcRef());
$package->endElement(); # Charity
}
if ($this->haveGasds()) {
foreach ($this->gasdsYear as $key => $val) {
$package->startElement('GASDSClaim');
$package->writeElement('Year', $this->gasdsYear[$key]);
$package->writeElement('Amount', number_format($this->gasdsAmount[$key], 2, '.', ''));
$package->endElement(); # GASDSClaim
}
}

$package->writeElement('CommBldgs', ($this->haveCbcd == true) ? 'yes' : 'no');
foreach ($this->cbcdAddr as $key => $val) {
$package->startElement('Building');
$package->writeElement('BldgName', $this->cbcdBldg[$key]);
$package->writeElement('Address', $this->cbcdAddr[$key]);
$package->writeElement('Postcode', $this->cbcdPoCo[$key]);
$package->startElement('BldgClaim');
$package->writeElement('Year', $this->cbcdYear[$key]);
$package->writeElement('Amount', number_format($this->cbcdAmount[$key], 2, '.', ''));
$package->endElement(); # BldgClaim
$package->endElement(); # Building
$package->startElement('GASDS');
$package->writeElement(
'ConnectedCharities',
$this->getClaimingOrganisation()->getHasConnectedCharities() ? 'yes' : 'no'
);
foreach ($this->getClaimingOrganisation()->getConnectedCharities() as $cc) {
$package->startElement('Charity');
$package->writeElement('Name', $cc->getName());
$package->writeElement('HMRCref', $cc->getHmrcRef());
$package->endElement(); # Charity
}
if ($this->haveGasds()) {
foreach ($this->gasdsYear as $key => $val) {
$package->startElement('GASDSClaim');
$package->writeElement('Year', $this->gasdsYear[$key]);
$package->writeElement('Amount', number_format($this->gasdsAmount[$key], 2, '.', ''));
$package->endElement(); # GASDSClaim
}
}

if (!empty($this->gasdsAdjustment)) {
$package->writeElement('Adj', number_format($this->gasdsAdjustment, 2, '.', ''));
}
$package->writeElement('CommBldgs', ($this->haveCbcd == true) ? 'yes' : 'no');
foreach ($this->cbcdAddr as $key => $val) {
$package->startElement('Building');
$package->writeElement('BldgName', $this->cbcdBldg[$key]);
$package->writeElement('Address', $this->cbcdAddr[$key]);
$package->writeElement('Postcode', $this->cbcdPoCo[$key]);
$package->startElement('BldgClaim');
$package->writeElement('Year', $this->cbcdYear[$key]);
$package->writeElement('Amount', number_format($this->cbcdAmount[$key], 2, '.', ''));
$package->endElement(); # BldgClaim
$package->endElement(); # Building
}

$package->endElement(); # GASDS
if (!empty($this->gasdsAdjustment)) {
$package->writeElement('Adj', number_format($this->gasdsAdjustment, 2, '.', ''));
}

$package->endElement(); # GASDS

$otherInfo = [];
if (!empty($this->gasdsAdjustment)) {
$otherInfo[] = $this->gasdsAdjReason;
Expand Down
23 changes: 11 additions & 12 deletions tests/GovTalk/GiftAid/GiftAidTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -401,9 +401,9 @@ public function testClaimWithCommunityBuildingsSubmissionAck(): void
$this->assertSame('A19FA1A31BCB42D887EA323292AACD88', $response['correlationid']);
}

public function testMultiClaimSubmissionAck(): void
public function testNomineeClaimSubmissionAck(): void
{
$this->setMockHttpResponse('SubmitMultiAckResponse.xml');
$this->setMockHttpResponse('SubmitNomineeClaimAckResponse.xml');
$this->gaService = $this->setUpService(); // Use client w/ mock queue.

$this->gaService->setAuthorisedOfficial($this->officer);
Expand All @@ -428,9 +428,9 @@ public function testMultiClaimSubmissionAck(): void
$this->assertSame('9072983591062099772', $response['correlationid']);
}

public function testMultiClaimSubmissionAckWithOfficialAndAgentOptionalFields(): void
public function testNomineeClaimSubmissionAckWithOfficialAndAgentOptionalFields(): void
{
$this->setMockHttpResponse('SubmitMultiAckResponse.xml');
$this->setMockHttpResponse('SubmitNomineeClaimAckResponse.xml');
$this->gaService = $this->setUpService(); // Use client w/ mock queue.

$this->officer->setTitle('Mx');
Expand All @@ -457,9 +457,9 @@ public function testMultiClaimSubmissionAckWithOfficialAndAgentOptionalFields():
$this->assertSame('9072983591062099772', $response['correlationid']);
}

public function testMultiClaimSubmissionWithFirstDonationNamesMissing(): void
public function testNomineeClaimSubmissionWithFirstDonationNamesMissing(): void
{
$this->setMockHttpResponse('SubmitMultiMissingNamesResponse.xml');
$this->setMockHttpResponse('SubmitNomineeClaimMissingNamesResponse.xml');
$this->gaService = $this->setUpService(); // Use client w/ mock queue.

// Provide identifier to trace donation errors, and give the 0th donation one by clearing
Expand Down Expand Up @@ -510,11 +510,11 @@ public function testMultiClaimSubmissionWithFirstDonationNamesMissing(): void
$this->assertEquals(['some-uuid-1234'], $response['donation_ids_with_errors']);
}

public function testMultiClaimSubmissionWithMultipleDonationErrors(): void
public function testNomineeClaimSubmissionWithMultipleDonationErrors(): void
{
// We separately test this to be confident the correct errors map back
// to the correct donation IDs.
$this->setMockHttpResponse('SubmitMultiMultipleErrorsResponse.xml');
$this->setMockHttpResponse('SubmitNomineeClaimMultipleErrorsResponse.xml');
$this->gaService = $this->setUpService(); // Use client w/ mock queue.

// Provide identifier to trace donation errors, and give the 0th donation one by clearing
Expand Down Expand Up @@ -588,9 +588,9 @@ public function testDeclarationResponsePoll(): void
$this->assertSame('A19FA1A31BCB42D887EA323292AACD88', $response['correlationid']);
}

public function testMultiClaimPollSuccessAndResponseDataFormat(): void
public function testPollSuccessAndResponseDataFormat(): void
{
$this->setMockHttpResponse('MultiClaimResponsePoll.xml');
$this->setMockHttpResponse('ClaimResponsePoll.xml');
$this->gaService = $this->setUpService(); // Use client w/ mock queue.

$response = $this->gaService->declarationResponsePoll(
Expand Down Expand Up @@ -663,6 +663,7 @@ private function addValidTestAgent(GiftAid $giftAidService, bool $withOptionalFi
'surname' => 'Bravo',
],
'email' => '[email protected]',
'telephone' => '01111 111112',
];

$agentAddress = [
Expand All @@ -674,8 +675,6 @@ private function addValidTestAgent(GiftAid $giftAidService, bool $withOptionalFi

if ($withOptionalFields) {
$agentContact['fax'] = '01111 111111';
$agentContact['telephone'] = '01111 111112';

$agentAddress['postcode'] = 'N1 1AA';
}

Expand Down
1 change: 1 addition & 0 deletions tests/GovTalk/GiftAid/Mock/ClaimResponsePoll.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version='1.0' encoding='UTF-8'?><GovTalkMessage xmlns="http://www.govtalk.gov.uk/CM/envelope"><EnvelopeVersion>2.0</EnvelopeVersion><Header><MessageDetails><Class>HMRC-CHAR-CLM</Class><Qualifier>response</Qualifier><Function>submit</Function><TransactionID>123</TransactionID><CorrelationID>456</CorrelationID><ResponseEndPoint PollInterval="10">https://secure.dev.gateway.gov.uk/poll</ResponseEndPoint><Transformation>XML</Transformation><GatewayTimestamp>2022-04-04T14:56:57.187</GatewayTimestamp></MessageDetails><SenderDetails/></Header><GovTalkDetails><Keys></Keys></GovTalkDetails><Body><SuccessResponse xmlns="http://www.inlandrevenue.gov.uk/SuccessResponse"><IRmarkReceipt><dsig:Signature xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:SignedInfo><dsig:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><dsig:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><dsig:Reference><dsig:Transforms><dsig:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"><dsig:XPath>(count(ancestor-or-self::node()|/gti:GovTalkMessage/gti:Body)=count(ancestor-or-self::node())) and (count(ancestor-or-self::node()|/gti:GovTalkMessage/gti:Body/*[name()='IRenvelope']/*[name()='IRheader']/*[name()='IRmark'])!=count(ancestor-or-self::node()))</dsig:XPath></dsig:Transform><dsig:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><dsig:DigestValue>someDigest</dsig:DigestValue></dsig:Reference></dsig:SignedInfo><dsig:SignatureValue>someSig==</dsig:SignatureValue><dsig:KeyInfo><dsig:X509Data><dsig:X509Certificate>someCert=</dsig:X509Certificate></dsig:X509Data></dsig:KeyInfo></dsig:Signature><Message code="0000">HMRC has received the HMRC-CHAR-CLM document ref: 92100000000000 at 08.53 on 04/04/2022. The associated IRmark was: 7QAAAAA000000AAAAAAAAA. We strongly recommend that you keep this receipt electronically, and we advise that you also keep your submission electronically for your records. They are evidence of the information that you submitted to HMRC.</Message></IRmarkReceipt><Message code="077001">Thank you for your submission</Message><AcceptedTime>2022-04-04T08:53:35.179</AcceptedTime></SuccessResponse></Body></GovTalkMessage>
Loading

0 comments on commit b932c2e

Please sign in to comment.