Skip to content

Commit 61e00ea

Browse files
authored
Merge pull request #589 from alphagov/PP-6076-optionally-include-moto-header-in-csv
PP-6076 Include MOTO column in CSV
2 parents 3892957 + cb8fc96 commit 61e00ea

7 files changed

Lines changed: 718 additions & 555 deletions

File tree

src/main/java/uk/gov/pay/ledger/transaction/model/CsvTransactionFactory.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public class CsvTransactionFactory {
5555
private static final String FIELD_CARD_TYPE = "Card Type";
5656
private static final String FIELD_FEE = "Fee";
5757
private static final String FIELD_NET = "Net";
58+
private static final String FIELD_MOTO = "MOTO";
5859
private ObjectMapper objectMapper;
5960

6061
@Inject
@@ -108,6 +109,7 @@ public Map<String, Object> toMap(TransactionEntity transactionEntity) {
108109
result.put(FIELD_CORPORATE_CARD_SURCHARGE, penceToCurrency(
109110
Optional.ofNullable(safeGetAsLong(transactionDetails, "corporate_surcharge")).orElse(0L)
110111
));
112+
result.put(FIELD_MOTO, transactionEntity.isMoto());
111113

112114
if (transactionEntity.getState() != null) {
113115
ExternalTransactionState state = ExternalTransactionState.from(transactionEntity.getState(), 2);
@@ -154,7 +156,9 @@ private Map<String, Object> getPaymentTransactionAttributes(TransactionEntity tr
154156
return result;
155157
}
156158

157-
public Map<String, Object> getCsvHeadersWithMedataKeys(List<String> metadataKeys, boolean includeFeeHeaders) {
159+
public Map<String, Object> getCsvHeadersWithMedataKeys(List<String> metadataKeys,
160+
boolean includeFeeHeaders,
161+
boolean includeMotoHeader) {
158162
LinkedHashMap<String, Object> headers = new LinkedHashMap<>();
159163

160164
headers.put(FIELD_REFERENCE, FIELD_REFERENCE);
@@ -185,6 +189,10 @@ public Map<String, Object> getCsvHeadersWithMedataKeys(List<String> metadataKeys
185189

186190
headers.put(FIELD_CARD_TYPE, FIELD_CARD_TYPE);
187191

192+
if (includeMotoHeader) {
193+
headers.put(FIELD_MOTO, FIELD_MOTO);
194+
}
195+
188196
if (metadataKeys != null) {
189197
metadataKeys.stream().sorted()
190198
.forEach(key -> {

src/main/java/uk/gov/pay/ledger/transaction/resource/TransactionResource.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public Response streamCsv(@Valid @BeanParam TransactionSearchParams searchParams
101101
@QueryParam("override_account_id_restriction") Boolean overrideAccountRestriction,
102102
@QueryParam("account_id") String gatewayAccountId,
103103
@QueryParam("fee_headers") boolean includeFeeHeaders,
104+
@QueryParam("moto_header") boolean includeMotoHeader,
104105
@Context UriInfo uriInfo) {
105106
StreamingOutput stream = outputStream -> {
106107
TransactionSearchParams csvSearchParams = Optional.ofNullable(searchParams).orElse(new TransactionSearchParams());
@@ -111,7 +112,7 @@ public Response streamCsv(@Valid @BeanParam TransactionSearchParams searchParams
111112
Long startingAfterId = null;
112113
int count = 0;
113114

114-
Map<String, Object> headers = csvService.csvHeaderFrom(searchParams, includeFeeHeaders);
115+
Map<String, Object> headers = csvService.csvHeaderFrom(searchParams, includeFeeHeaders, includeMotoHeader);
115116
ObjectWriter writer = csvService.writerFrom(headers);
116117
Stopwatch stopwatch = Stopwatch.createStarted();
117118
outputStream.write(csvService.csvStringFrom(headers, writer).getBytes());

src/main/java/uk/gov/pay/ledger/transaction/service/CsvService.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@ public ObjectWriter writerFrom(Map<String, Object> headers) {
3939
return mapper.writer(schema);
4040
}
4141

42-
public Map<String, Object> csvHeaderFrom(TransactionSearchParams searchParams, boolean includeFeeHeaders) {
42+
public Map<String, Object> csvHeaderFrom(TransactionSearchParams searchParams,
43+
boolean includeFeeHeaders,
44+
boolean includeMotoHeader) {
4345
List<String> metadataKeys = transactionMetadataService.findMetadataKeysForTransactions(searchParams);
44-
return csvTransactionFactory.getCsvHeadersWithMedataKeys(metadataKeys, includeFeeHeaders);
46+
return csvTransactionFactory.getCsvHeadersWithMedataKeys(metadataKeys, includeFeeHeaders, includeMotoHeader);
4547
}
4648

4749
public String csvStringFrom(Map<String, Object> headers, ObjectWriter writer) throws JsonProcessingException {

src/test/java/uk/gov/pay/ledger/transaction/model/CsvTransactionFactoryTest.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,10 @@ public void toMapShouldIncludeFeeAndNetAmountForStripePayments() {
127127
}
128128

129129
@Test
130-
public void getCsvHeadersWithMedataKeysShouldReturnMapWithCorrectCsvHeaders_WithoutFeeColumns() {
130+
public void getCsvHeadersWithMedataKeysShouldReturnMapWithCorrectCsvHeaders_WithoutOptionalColumns() {
131131
var keys = List.of("test-key-1", "test-key-2");
132132

133-
Map<String, Object> csvHeaders = csvTransactionFactory.getCsvHeadersWithMedataKeys(keys, false);
133+
Map<String, Object> csvHeaders = csvTransactionFactory.getCsvHeadersWithMedataKeys(keys, false, false);
134134

135135
assertThat(csvHeaders.get("Reference"), is(notNullValue()));
136136
assertThat(csvHeaders.get("Description"), is(notNullValue()));
@@ -158,18 +158,28 @@ public void getCsvHeadersWithMedataKeysShouldReturnMapWithCorrectCsvHeaders_With
158158

159159
assertThat(csvHeaders.get("Net"), is(nullValue()));
160160
assertThat(csvHeaders.get("Fee"), is(nullValue()));
161+
assertThat(csvHeaders.get("MOTO"), is(nullValue()));
161162
}
162163

163164
@Test
164165
public void getCsvHeadersWithMedataKeysShouldReturnMapWithCorrectCsvHeaders_WithFeeColumns() {
165166
var keys = List.of("test-key-1", "test-key-2");
166167

167-
Map<String, Object> csvHeaders = csvTransactionFactory.getCsvHeadersWithMedataKeys(keys, true);
168+
Map<String, Object> csvHeaders = csvTransactionFactory.getCsvHeadersWithMedataKeys(keys, true, false);
168169

169170
assertThat(csvHeaders.get("Net"), is(notNullValue()));
170171
assertThat(csvHeaders.get("Fee"), is(notNullValue()));
171172
}
172173

174+
@Test
175+
public void getCsvHeadersWithMedataKeysShouldReturnMapWithCorrectCsvHeaders_WithMotoColumn() {
176+
var keys = List.of("test-key-1", "test-key-2");
177+
178+
Map<String, Object> csvHeaders = csvTransactionFactory.getCsvHeadersWithMedataKeys(keys, false, true);
179+
180+
assertThat(csvHeaders.get("MOTO"), is(notNullValue()));
181+
}
182+
173183
@Test
174184
public void shouldSanitiseValuesCorrectlyAgainstSpreadsheetFormulaInjection(){
175185
TransactionEntity transactionEntity = transactionFixture.withNetAmount(594)
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
package uk.gov.pay.ledger.transaction.resource;
2+
3+
import com.google.common.collect.ImmutableMap;
4+
import org.apache.commons.csv.CSVParser;
5+
import org.apache.commons.csv.CSVRecord;
6+
import org.junit.Before;
7+
import org.junit.ClassRule;
8+
import org.junit.Test;
9+
import uk.gov.pay.ledger.metadatakey.dao.MetadataKeyDao;
10+
import uk.gov.pay.ledger.rule.AppWithPostgresAndSqsRule;
11+
import uk.gov.pay.ledger.transaction.state.TransactionState;
12+
import uk.gov.pay.ledger.transactionmetadata.dao.TransactionMetadataDao;
13+
import uk.gov.pay.ledger.util.fixture.TransactionFixture;
14+
15+
import javax.ws.rs.core.Response;
16+
import java.io.IOException;
17+
import java.io.InputStream;
18+
import java.time.ZonedDateTime;
19+
import java.util.List;
20+
21+
import static io.restassured.RestAssured.given;
22+
import static java.nio.charset.StandardCharsets.UTF_8;
23+
import static org.apache.commons.csv.CSVFormat.DEFAULT;
24+
import static org.apache.commons.csv.CSVFormat.RFC4180;
25+
import static org.hamcrest.CoreMatchers.is;
26+
import static org.hamcrest.MatcherAssert.assertThat;
27+
import static uk.gov.pay.ledger.util.DatabaseTestHelper.aDatabaseTestHelper;
28+
import static uk.gov.pay.ledger.util.fixture.TransactionFixture.aTransactionFixture;
29+
30+
public class TransactionResourceCsvIT {
31+
32+
@ClassRule
33+
public static AppWithPostgresAndSqsRule rule = new AppWithPostgresAndSqsRule();
34+
35+
private Integer port = rule.getAppRule().getLocalPort();
36+
private MetadataKeyDao metadataKeyDao;
37+
private TransactionMetadataDao transactionMetadataDao;
38+
39+
@Before
40+
public void setUp() {
41+
transactionMetadataDao = new TransactionMetadataDao(rule.getJdbi());
42+
metadataKeyDao = rule.getJdbi().onDemand(MetadataKeyDao.class);
43+
aDatabaseTestHelper(rule.getJdbi()).truncateAllData();
44+
}
45+
46+
@Test
47+
public void shouldGetAllTransactionsAsCSVWithAcceptType() throws IOException {
48+
String targetGatewayAccountId = "123";
49+
String otherGatewayAccountId = "456";
50+
51+
TransactionFixture transactionFixture = aTransactionFixture()
52+
.withTransactionType("PAYMENT")
53+
.withState(TransactionState.ERROR_GATEWAY)
54+
.withAmount(123L)
55+
.withTotalAmount(123L)
56+
.withCorporateCardSurcharge(5L)
57+
.withCreatedDate(ZonedDateTime.parse("2018-03-12T16:25:01.123456Z"))
58+
.withGatewayAccountId(targetGatewayAccountId)
59+
.withLastDigitsCardNumber("1234")
60+
.withCardBrandLabel("Diners Club")
61+
.withDefaultCardDetails().withCardholderName("J Doe")
62+
.withGatewayTransactionId("gateway-transaction-id")
63+
.withExternalMetadata(ImmutableMap.of("test-key-1", "value1"))
64+
.withDefaultTransactionDetails()
65+
.insert(rule.getJdbi());
66+
67+
aTransactionFixture()
68+
.withTransactionType("PAYMENT")
69+
.withState(TransactionState.SUBMITTED)
70+
.withGatewayAccountId(otherGatewayAccountId)
71+
.insert(rule.getJdbi());
72+
73+
aTransactionFixture()
74+
.withTransactionType("REFUND")
75+
.withParentExternalId(transactionFixture.getExternalId())
76+
.withGatewayAccountId(transactionFixture.getGatewayAccountId())
77+
.withRefundedByUserEmail("refund-by-user-email@example.org")
78+
.withCreatedDate(ZonedDateTime.parse("2018-03-12T16:24:01.123456Z"))
79+
.withAmount(100L)
80+
.withTotalAmount(100L)
81+
.withState(TransactionState.ERROR_GATEWAY)
82+
.withDefaultTransactionDetails()
83+
.insert(rule.getJdbi());
84+
85+
metadataKeyDao.insertIfNotExist("test-key-1");
86+
metadataKeyDao.insertIfNotExist("test-key-2");
87+
88+
transactionMetadataDao.insertIfNotExist(transactionFixture.getId(), "test-key-1");
89+
90+
InputStream csvResponseStream = given().port(port)
91+
.accept("text/csv")
92+
.get("/v1/transaction/?" +
93+
"account_id=" + targetGatewayAccountId +
94+
"&page=1" +
95+
"&display_size=5"
96+
)
97+
.then()
98+
.statusCode(Response.Status.OK.getStatusCode())
99+
.contentType("text/csv")
100+
.extract().asInputStream();
101+
102+
List<CSVRecord> csvRecords = CSVParser.parse(csvResponseStream, UTF_8, RFC4180.withFirstRecordAsHeader()).getRecords();
103+
104+
assertThat(csvRecords.size(), is(2));
105+
106+
CSVRecord paymentRecord = csvRecords.get(0);
107+
assertThat(paymentRecord.size(), is(22));
108+
assertPaymentTransactionDetails(paymentRecord, transactionFixture);
109+
assertThat(paymentRecord.get("Amount"), is("1.23"));
110+
assertThat(paymentRecord.get("State"), is("Error"));
111+
assertThat(paymentRecord.get("Finished"), is("true"));
112+
assertThat(paymentRecord.get("Error Code"), is("P0050"));
113+
assertThat(paymentRecord.get("Error Message"), is("Payment provider returned an error"));
114+
assertThat(paymentRecord.get("Date Created"), is("12 Mar 2018"));
115+
assertThat(paymentRecord.get("Time Created"), is("16:25:01"));
116+
assertThat(paymentRecord.get("Corporate Card Surcharge"), is("0.05"));
117+
assertThat(paymentRecord.get("Total Amount"), is("1.23"));
118+
assertThat(paymentRecord.get("test-key-1 (metadata)"), is("value1"));
119+
assertThat(paymentRecord.get("Wallet Type"), is(""));
120+
assertThat(paymentRecord.isMapped("Net"), is(false));
121+
assertThat(paymentRecord.isMapped("Fee"), is(false));
122+
assertThat(paymentRecord.isMapped("MOTO"), is(false));
123+
124+
CSVRecord refundRecord = csvRecords.get(1);
125+
assertPaymentTransactionDetails(refundRecord, transactionFixture);
126+
assertThat(refundRecord.get("Amount"), is("-1.00"));
127+
assertThat(refundRecord.get("State"), is("Refund error"));
128+
assertThat(refundRecord.get("Finished"), is("true"));
129+
assertThat(refundRecord.get("Error Code"), is("P0050"));
130+
assertThat(refundRecord.get("Error Message"), is("Payment provider returned an error"));
131+
assertThat(refundRecord.get("Date Created"), is("12 Mar 2018"));
132+
assertThat(refundRecord.get("Time Created"), is("16:24:01"));
133+
assertThat(refundRecord.get("Corporate Card Surcharge"), is("0.00"));
134+
assertThat(refundRecord.get("Total Amount"), is("-1.00"));
135+
assertThat(refundRecord.get("Wallet Type"), is(""));
136+
assertThat(refundRecord.get("Issued By"), is("refund-by-user-email@example.org"));
137+
}
138+
139+
@Test
140+
public void shouldGetAllTransactionsAsCSVWithAcceptTypeWithFeeHeaders() throws IOException {
141+
String gatewayAccountId = "123";
142+
143+
aTransactionFixture()
144+
.withGatewayAccountId(gatewayAccountId)
145+
.withTransactionType("PAYMENT")
146+
.withFee(100)
147+
.withNetAmount(1100)
148+
.insert(rule.getJdbi());
149+
150+
InputStream csvResponseStream = given().port(port)
151+
.accept("text/csv")
152+
.get("/v1/transaction/?" +
153+
"account_id=" + gatewayAccountId +
154+
"&fee_headers=true" +
155+
"&page=1" +
156+
"&display_size=5"
157+
)
158+
.then()
159+
.statusCode(Response.Status.OK.getStatusCode())
160+
.contentType("text/csv")
161+
.extract().asInputStream();
162+
163+
List<CSVRecord> csvRecords = CSVParser.parse(csvResponseStream, UTF_8, RFC4180.withFirstRecordAsHeader()).getRecords();
164+
165+
assertThat(csvRecords.size(), is(1));
166+
167+
CSVRecord paymentRecord = csvRecords.get(0);
168+
assertThat(paymentRecord.size(), is(23));
169+
assertThat(paymentRecord.get("Net"), is("11.00"));
170+
assertThat(paymentRecord.get("Fee"), is("1.00"));
171+
}
172+
173+
@Test
174+
public void shouldGetAllTransactionsAsCSVWithAcceptTypeWithMotoHeader() throws IOException {
175+
String gatewayAccountId = "123";
176+
177+
aTransactionFixture()
178+
.withGatewayAccountId(gatewayAccountId)
179+
.withTransactionType("PAYMENT")
180+
.withMoto(true)
181+
.insert(rule.getJdbi());
182+
183+
InputStream csvResponseStream = given().port(port)
184+
.accept("text/csv")
185+
.get("/v1/transaction/?" +
186+
"account_id=" + gatewayAccountId +
187+
"&moto_header=true" +
188+
"&page=1" +
189+
"&display_size=5"
190+
)
191+
.then()
192+
.statusCode(Response.Status.OK.getStatusCode())
193+
.contentType("text/csv")
194+
.extract().asInputStream();
195+
196+
List<CSVRecord> csvRecords = CSVParser.parse(csvResponseStream, UTF_8, RFC4180.withFirstRecordAsHeader()).getRecords();
197+
198+
assertThat(csvRecords.size(), is(1));
199+
200+
CSVRecord paymentRecord = csvRecords.get(0);
201+
assertThat(paymentRecord.size(), is(22));
202+
assertThat(paymentRecord.get("MOTO"), is("true"));
203+
}
204+
205+
@Test
206+
public void shouldReturnCSVHeadersInCorrectOrder() throws IOException {
207+
String targetGatewayAccountId = "123";
208+
209+
String metadataKey = "a-metadata-key";
210+
TransactionFixture transactionFixture = aTransactionFixture()
211+
.withTransactionType("PAYMENT")
212+
.withGatewayAccountId(targetGatewayAccountId)
213+
.withExternalMetadata(ImmutableMap.of(metadataKey, "value1"))
214+
.withFee(100)
215+
.withNetAmount(1100)
216+
.withDefaultTransactionDetails()
217+
.insert(rule.getJdbi());
218+
219+
metadataKeyDao.insertIfNotExist(metadataKey);
220+
transactionMetadataDao.insertIfNotExist(transactionFixture.getId(), metadataKey);
221+
222+
InputStream csvResponseStream = given().port(port)
223+
.accept("text/csv")
224+
.get("/v1/transaction?" +
225+
"account_id=" + targetGatewayAccountId +
226+
"&fee_headers=true" +
227+
"&moto_header=true" +
228+
"&page=1" +
229+
"&display_size=5"
230+
)
231+
.then()
232+
.statusCode(Response.Status.OK.getStatusCode())
233+
.contentType("text/csv")
234+
.extract().asInputStream();
235+
236+
List<CSVRecord> csvRecords = CSVParser.parse(csvResponseStream, UTF_8, DEFAULT).getRecords();
237+
238+
CSVRecord header = csvRecords.get(0);
239+
assertThat(header.size(), is(25));
240+
assertThat(header.get(0), is("Reference"));
241+
assertThat(header.get(1), is("Description"));
242+
assertThat(header.get(2), is("Email"));
243+
assertThat(header.get(3), is("Amount"));
244+
assertThat(header.get(4), is("Card Brand"));
245+
assertThat(header.get(5), is("Cardholder Name"));
246+
assertThat(header.get(6), is("Card Expiry Date"));
247+
assertThat(header.get(7), is("Card Number"));
248+
assertThat(header.get(8), is("State"));
249+
assertThat(header.get(9), is("Finished"));
250+
assertThat(header.get(10), is("Error Code"));
251+
assertThat(header.get(11), is("Error Message"));
252+
assertThat(header.get(12), is("Provider ID"));
253+
assertThat(header.get(13), is("GOV.UK Payment ID"));
254+
assertThat(header.get(14), is("Issued By"));
255+
assertThat(header.get(15), is("Date Created"));
256+
assertThat(header.get(16), is("Time Created"));
257+
assertThat(header.get(17), is("Corporate Card Surcharge"));
258+
assertThat(header.get(18), is("Total Amount"));
259+
assertThat(header.get(19), is("Wallet Type"));
260+
assertThat(header.get(20), is("Fee"));
261+
assertThat(header.get(21), is("Net"));
262+
assertThat(header.get(22), is("Card Type"));
263+
assertThat(header.get(23), is("MOTO"));
264+
assertThat(header.get(24), is("a-metadata-key (metadata)"));
265+
}
266+
267+
private void assertPaymentTransactionDetails(CSVRecord csvRecord, TransactionFixture transactionFixture) {
268+
assertThat(csvRecord.get("Reference"), is(transactionFixture.getReference()));
269+
assertThat(csvRecord.get("Description"), is(transactionFixture.getDescription()));
270+
assertThat(csvRecord.get("Email"), is("someone@example.org"));
271+
assertThat(csvRecord.get("Card Brand"), is("Diners Club"));
272+
assertThat(csvRecord.get("Cardholder Name"), is("J Doe"));
273+
assertThat(csvRecord.get("Card Expiry Date"), is("10/21"));
274+
assertThat(csvRecord.get("Card Number"), is("1234"));
275+
assertThat(csvRecord.get("Provider ID"), is("gateway-transaction-id"));
276+
assertThat(csvRecord.get("GOV.UK Payment ID"), is(transactionFixture.getExternalId()));
277+
assertThat(csvRecord.get("Card Type"), is("credit"));
278+
}
279+
}

0 commit comments

Comments
 (0)