biller-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- biller_cli/__init__.py +0 -0
- biller_cli/ai/__init__.py +397 -0
- biller_cli/ai/ingest.py +394 -0
- biller_cli/commands/down.py +96 -0
- biller_cli/commands/downgrade.py +142 -0
- biller_cli/commands/init.py +210 -0
- biller_cli/commands/up.py +138 -0
- biller_cli/commands/upgrade.py +271 -0
- biller_cli/main.py +34 -0
- biller_cli/templates/biller-user-layer/pom.xml +100 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/ApplicationContext.java +13 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/config/DatabaseConfig.java +35 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/config/WebCorsConfig.java +21 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/controller/BbpsRequestController.java +98 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/dao/impl/BillFetchDaoImpl.java +135 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/dao/impl/BillPaymentDaoImpl.java +100 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/BillingProviderConfig.java +34 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/BillingProviderProperties.java +92 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/impl/CsvBillingProvider.java +257 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/impl/ExcelBillingProvider.java +261 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/impl/PostgresBillingProvider.java +210 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/service/impl/BillFetchServiceImpl.java +131 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/service/impl/BillPaymentServiceImpl.java +175 -0
- biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/service/impl/HeartbeatServiceImpl.java +136 -0
- biller_cli/templates/biller-user-layer/src/main/resources/application.properties +88 -0
- biller_cli/templates/pom.xml +33 -0
- biller_cli/utils/preflight.py +151 -0
- biller_cli/utils/secrets.py +37 -0
- biller_cli/utils/version_pin.py +67 -0
- biller_cli-0.1.0.dist-info/METADATA +17 -0
- biller_cli-0.1.0.dist-info/RECORD +33 -0
- biller_cli-0.1.0.dist-info/WHEEL +4 -0
- biller_cli-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
package bharat.connect.biller.provider.impl;
|
|
2
|
+
|
|
3
|
+
import bharat.connect.biller.model.BillDetails;
|
|
4
|
+
import bharat.connect.biller.provider.BillingProvider;
|
|
5
|
+
import bharat.connect.biller.provider.BillingProviderProperties;
|
|
6
|
+
import bharat.connect.biller.provider.CustomerParamCriterion;
|
|
7
|
+
import bharat.connect.biller.provider.PaymentUpdateRequest;
|
|
8
|
+
import org.springframework.beans.factory.annotation.Qualifier;
|
|
9
|
+
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
|
10
|
+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
|
11
|
+
import org.springframework.transaction.annotation.Transactional;
|
|
12
|
+
|
|
13
|
+
import java.sql.ResultSet;
|
|
14
|
+
import java.sql.SQLException;
|
|
15
|
+
import java.util.List;
|
|
16
|
+
import java.util.regex.Pattern;
|
|
17
|
+
|
|
18
|
+
public class PostgresBillingProvider implements BillingProvider {
|
|
19
|
+
private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("^[A-Za-z_][A-Za-z0-9_\\.]*$");
|
|
20
|
+
|
|
21
|
+
private final NamedParameterJdbcTemplate jdbcNamedTemplate;
|
|
22
|
+
private final BillingProviderProperties.Postgres postgres;
|
|
23
|
+
|
|
24
|
+
public PostgresBillingProvider(@Qualifier("jdbcNamedTemplate") NamedParameterJdbcTemplate jdbcNamedTemplate,
|
|
25
|
+
BillingProviderProperties properties) {
|
|
26
|
+
this.jdbcNamedTemplate = jdbcNamedTemplate;
|
|
27
|
+
this.postgres = properties.getPostgres();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Override
|
|
31
|
+
public BillDetails findLatestUnpaidBill(List<CustomerParamCriterion> criteria) {
|
|
32
|
+
if (criteria == null || criteria.isEmpty()) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
String billTable = id(postgres.getTables().getBillDetails());
|
|
37
|
+
String billIdCol = id(postgres.getBillColumns().getBillId());
|
|
38
|
+
String customerParamNameCol = id(postgres.getBillColumns().getCustomerParamName());
|
|
39
|
+
String customerParamTypeCol = id(postgres.getBillColumns().getCustomerParamType());
|
|
40
|
+
String customerParamValueCol = id(postgres.getBillColumns().getCustomerParamValue());
|
|
41
|
+
String billAmountCol = id(postgres.getBillColumns().getBillAmount());
|
|
42
|
+
String billDateCol = id(postgres.getBillColumns().getBillDate());
|
|
43
|
+
String dueDateCol = id(postgres.getBillColumns().getDueDate());
|
|
44
|
+
String billNumberCol = id(postgres.getBillColumns().getBillNumber());
|
|
45
|
+
String billPeriodCol = id(postgres.getBillColumns().getBillPeriod());
|
|
46
|
+
String billStatusCol = id(postgres.getBillColumns().getBillStatus());
|
|
47
|
+
String additionalInfoCol = id(postgres.getBillColumns().getAdditionalInfo());
|
|
48
|
+
String createdAtCol = id(postgres.getBillColumns().getCreatedAt());
|
|
49
|
+
String updatedAtCol = id(postgres.getBillColumns().getUpdatedAt());
|
|
50
|
+
|
|
51
|
+
StringBuilder sql = new StringBuilder("""
|
|
52
|
+
SELECT %s AS bill_id, %s AS customer_param_name, %s AS customer_param_type, %s AS customer_param_value,
|
|
53
|
+
%s AS bill_amount, %s AS bill_date, %s AS due_date, %s AS bill_number, %s AS bill_period,
|
|
54
|
+
%s AS bill_status, %s AS additional_info, %s AS created_at, %s AS updated_at
|
|
55
|
+
FROM %s
|
|
56
|
+
WHERE %s = 'UNPAID'
|
|
57
|
+
""".formatted(
|
|
58
|
+
billIdCol, customerParamNameCol, customerParamTypeCol, customerParamValueCol,
|
|
59
|
+
billAmountCol, billDateCol, dueDateCol, billNumberCol, billPeriodCol, billStatusCol,
|
|
60
|
+
additionalInfoCol, createdAtCol, updatedAtCol, billTable, billStatusCol
|
|
61
|
+
));
|
|
62
|
+
|
|
63
|
+
MapSqlParameterSource params = new MapSqlParameterSource();
|
|
64
|
+
appendCriteriaClause(criteria, sql, params, customerParamNameCol, customerParamValueCol);
|
|
65
|
+
|
|
66
|
+
sql.append("""
|
|
67
|
+
ORDER BY %s NULLS LAST, %s DESC, %s DESC
|
|
68
|
+
LIMIT 1
|
|
69
|
+
""".formatted(dueDateCol, billDateCol, billIdCol));
|
|
70
|
+
|
|
71
|
+
List<BillDetails> list = jdbcNamedTemplate.query(sql.toString(), params, (rs, rowNum) -> mapBillDetails(rs));
|
|
72
|
+
return list.isEmpty() ? null : list.get(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@Override
|
|
76
|
+
@Transactional
|
|
77
|
+
public boolean markBillPaidAndRecordTransaction(List<CustomerParamCriterion> criteria, PaymentUpdateRequest paymentUpdateRequest) {
|
|
78
|
+
if (criteria == null || criteria.isEmpty() || paymentUpdateRequest == null) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
String billTable = id(postgres.getTables().getBillDetails());
|
|
83
|
+
String paymentTable = id(postgres.getTables().getPaymentTransactions());
|
|
84
|
+
String billIdCol = id(postgres.getBillColumns().getBillId());
|
|
85
|
+
String customerParamNameCol = id(postgres.getBillColumns().getCustomerParamName());
|
|
86
|
+
String customerParamValueCol = id(postgres.getBillColumns().getCustomerParamValue());
|
|
87
|
+
String billStatusCol = id(postgres.getBillColumns().getBillStatus());
|
|
88
|
+
String updatedAtCol = id(postgres.getBillColumns().getUpdatedAt());
|
|
89
|
+
String dueDateCol = id(postgres.getBillColumns().getDueDate());
|
|
90
|
+
String billDateCol = id(postgres.getBillColumns().getBillDate());
|
|
91
|
+
String paymentBillIdCol = id(postgres.getPaymentColumns().getBillId());
|
|
92
|
+
String paymentTxnRefCol = id(postgres.getPaymentColumns().getBbpsTxnRef());
|
|
93
|
+
String paymentAmountCol = id(postgres.getPaymentColumns().getAmountPaid());
|
|
94
|
+
String paymentModeCol = id(postgres.getPaymentColumns().getPaymentMode());
|
|
95
|
+
|
|
96
|
+
MapSqlParameterSource updateParams = new MapSqlParameterSource();
|
|
97
|
+
StringBuilder subWhere = new StringBuilder("%s = 'UNPAID'".formatted(billStatusCol));
|
|
98
|
+
appendCriteriaClause(criteria, subWhere, updateParams, customerParamNameCol, customerParamValueCol);
|
|
99
|
+
|
|
100
|
+
String updateSql = """
|
|
101
|
+
UPDATE %s
|
|
102
|
+
SET %s = 'PAID',
|
|
103
|
+
%s = now()
|
|
104
|
+
WHERE %s = (
|
|
105
|
+
SELECT %s FROM %s
|
|
106
|
+
WHERE %s
|
|
107
|
+
ORDER BY %s NULLS LAST, %s DESC, %s DESC
|
|
108
|
+
LIMIT 1
|
|
109
|
+
)
|
|
110
|
+
""".formatted(
|
|
111
|
+
billTable,
|
|
112
|
+
billStatusCol,
|
|
113
|
+
updatedAtCol,
|
|
114
|
+
billIdCol,
|
|
115
|
+
billIdCol,
|
|
116
|
+
billTable,
|
|
117
|
+
subWhere,
|
|
118
|
+
dueDateCol,
|
|
119
|
+
billDateCol,
|
|
120
|
+
billIdCol
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
int updatedRows = jdbcNamedTemplate.update(updateSql, updateParams);
|
|
124
|
+
if (updatedRows <= 0) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
String insertSql = """
|
|
129
|
+
INSERT INTO %s (%s, %s, %s, %s)
|
|
130
|
+
VALUES (:billId, :bbpsTxnRef, :amountPaid, :paymentMode)
|
|
131
|
+
""".formatted(paymentTable, paymentBillIdCol, paymentTxnRefCol, paymentAmountCol, paymentModeCol);
|
|
132
|
+
|
|
133
|
+
MapSqlParameterSource insertParams = new MapSqlParameterSource()
|
|
134
|
+
.addValue("billId", paymentUpdateRequest.getBillId())
|
|
135
|
+
.addValue("bbpsTxnRef", paymentUpdateRequest.getBbpsTxnRef())
|
|
136
|
+
.addValue("amountPaid", paymentUpdateRequest.getAmountPaid())
|
|
137
|
+
.addValue("paymentMode", paymentUpdateRequest.getPaymentMode());
|
|
138
|
+
|
|
139
|
+
int inserted = jdbcNamedTemplate.update(insertSql, insertParams);
|
|
140
|
+
return updatedRows > 0 && inserted > 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private void appendCriteriaClause(List<CustomerParamCriterion> criteria,
|
|
144
|
+
StringBuilder sql,
|
|
145
|
+
MapSqlParameterSource params,
|
|
146
|
+
String customerParamNameCol,
|
|
147
|
+
String customerParamValueCol) {
|
|
148
|
+
boolean addedAny = false;
|
|
149
|
+
for (int i = 0; i < criteria.size(); i++) {
|
|
150
|
+
CustomerParamCriterion criterion = criteria.get(i);
|
|
151
|
+
if (criterion == null) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
String nKey = "name" + i;
|
|
156
|
+
String vKey = "value" + i;
|
|
157
|
+
params.addValue(nKey, criterion.getName());
|
|
158
|
+
params.addValue(vKey, criterion.getValue());
|
|
159
|
+
|
|
160
|
+
sql.append(addedAny ? " OR " : " AND (");
|
|
161
|
+
sql.append("(").append(customerParamNameCol).append(" = :").append(nKey)
|
|
162
|
+
.append(" AND ").append(customerParamValueCol).append(" = :").append(vKey).append(")");
|
|
163
|
+
addedAny = true;
|
|
164
|
+
}
|
|
165
|
+
if (addedAny) {
|
|
166
|
+
sql.append(")");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private BillDetails mapBillDetails(ResultSet rs) throws SQLException {
|
|
171
|
+
BillDetails b = new BillDetails();
|
|
172
|
+
b.setBillId(rs.getLong("bill_id"));
|
|
173
|
+
b.setCustomerParamName(rs.getString("customer_param_name"));
|
|
174
|
+
b.setCustomerParamType(rs.getString("customer_param_type"));
|
|
175
|
+
b.setCustomerParamValue(rs.getString("customer_param_value"));
|
|
176
|
+
b.setBillAmount(rs.getBigDecimal("bill_amount"));
|
|
177
|
+
var billDate = rs.getDate("bill_date");
|
|
178
|
+
if (billDate != null) {
|
|
179
|
+
b.setBillDate(billDate.toLocalDate());
|
|
180
|
+
}
|
|
181
|
+
var dueDate = rs.getDate("due_date");
|
|
182
|
+
if (dueDate != null) {
|
|
183
|
+
b.setDueDate(dueDate.toLocalDate());
|
|
184
|
+
}
|
|
185
|
+
b.setBillNumber(rs.getString("bill_number"));
|
|
186
|
+
b.setBillPeriod(rs.getString("bill_period"));
|
|
187
|
+
b.setBillStatus(rs.getString("bill_status"));
|
|
188
|
+
b.setAdditionalInfo(rs.getString("additional_info"));
|
|
189
|
+
var created = rs.getTimestamp("created_at");
|
|
190
|
+
if (created != null) {
|
|
191
|
+
b.setCreatedAt(created.toLocalDateTime());
|
|
192
|
+
}
|
|
193
|
+
var updated = rs.getTimestamp("updated_at");
|
|
194
|
+
if (updated != null) {
|
|
195
|
+
b.setUpdatedAt(updated.toLocalDateTime());
|
|
196
|
+
}
|
|
197
|
+
return b;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private String id(String configuredValue) {
|
|
201
|
+
if (configuredValue == null || configuredValue.isBlank()) {
|
|
202
|
+
throw new IllegalArgumentException("Postgres table/column mapping value must not be blank");
|
|
203
|
+
}
|
|
204
|
+
String value = configuredValue.trim();
|
|
205
|
+
if (!IDENTIFIER_PATTERN.matcher(value).matches()) {
|
|
206
|
+
throw new IllegalArgumentException("Invalid postgres identifier: " + value);
|
|
207
|
+
}
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
package bharat.connect.biller.service.impl;
|
|
2
|
+
|
|
3
|
+
import bharat.connect.biller.common.CommonUtils;
|
|
4
|
+
import bharat.connect.biller.model.BillDetails;
|
|
5
|
+
import bharat.connect.biller.provider.BillingProvider;
|
|
6
|
+
import bharat.connect.biller.provider.CustomerParamCriteriaMapper;
|
|
7
|
+
import bharat.connect.biller.rest.OuRestTemplate;
|
|
8
|
+
import bharat.connect.biller.service.BillFetchService;
|
|
9
|
+
import org.bbps.schema.*;
|
|
10
|
+
import org.springframework.beans.factory.annotation.Autowired;
|
|
11
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
12
|
+
import org.springframework.http.MediaType;
|
|
13
|
+
import org.springframework.http.ResponseEntity;
|
|
14
|
+
import org.springframework.scheduling.annotation.Async;
|
|
15
|
+
import org.springframework.stereotype.Service;
|
|
16
|
+
|
|
17
|
+
import jakarta.xml.bind.JAXBContext;
|
|
18
|
+
import jakarta.xml.bind.JAXBException;
|
|
19
|
+
import jakarta.xml.bind.Marshaller;
|
|
20
|
+
import java.io.StringWriter;
|
|
21
|
+
|
|
22
|
+
@Service
|
|
23
|
+
public class BillFetchServiceImpl implements BillFetchService {
|
|
24
|
+
|
|
25
|
+
@Autowired
|
|
26
|
+
private BillingProvider billingProvider;
|
|
27
|
+
|
|
28
|
+
@Value("${ou.id}")
|
|
29
|
+
private String ouId;
|
|
30
|
+
|
|
31
|
+
@Value("${bbps.ip:${bou.domain:${bou.domain}}}")
|
|
32
|
+
private String bbpsIp;
|
|
33
|
+
|
|
34
|
+
@Value("${bbps.billfetchresponse.url:${bou.billfetchresponse.url:${bou.billfetchresponse.url}}}")
|
|
35
|
+
private String billFetchResponseUrl;
|
|
36
|
+
|
|
37
|
+
@Autowired
|
|
38
|
+
private final OuRestTemplate ouRestTemplate;
|
|
39
|
+
|
|
40
|
+
private static final JAXBContext jaxbContext;
|
|
41
|
+
|
|
42
|
+
static {
|
|
43
|
+
JAXBContext jaxbContext2 = null;
|
|
44
|
+
try {
|
|
45
|
+
jaxbContext2 = JAXBContext.newInstance(Ack.class, BillFetchResponse.class);
|
|
46
|
+
} catch (JAXBException e) {
|
|
47
|
+
e.printStackTrace();
|
|
48
|
+
jaxbContext2 = null;
|
|
49
|
+
}
|
|
50
|
+
jaxbContext = jaxbContext2;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public BillFetchServiceImpl(OuRestTemplate ouRestTemplate) {
|
|
54
|
+
this.ouRestTemplate = ouRestTemplate;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@Async
|
|
58
|
+
@Override
|
|
59
|
+
public void processBillFetchAsync(BillFetchRequest fetchRequest, String referenceId) {
|
|
60
|
+
System.out.println("Processing Bill Fetch");
|
|
61
|
+
if (fetchRequest == null || fetchRequest.getBillDetails() == null || fetchRequest.getBillDetails().getCustomerParams() == null) return;
|
|
62
|
+
BillDetails bd = billingProvider.findLatestUnpaidBill(
|
|
63
|
+
CustomerParamCriteriaMapper.from(fetchRequest.getBillDetails().getCustomerParams())
|
|
64
|
+
);
|
|
65
|
+
if (bd == null) {
|
|
66
|
+
System.out.println("No Bills found");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
BillFetchResponse fetchResponse = new BillFetchResponse();
|
|
72
|
+
fetchResponse.setHead(fetchRequest.getHead());
|
|
73
|
+
fetchResponse.setTxn(fetchRequest.getTxn());
|
|
74
|
+
String ts = CommonUtils.getFormattedCurrentTimestamp();
|
|
75
|
+
fetchResponse.getHead().setRefId(fetchRequest.getHead().getRefId());
|
|
76
|
+
fetchResponse.getHead().setTs(ts);
|
|
77
|
+
fetchResponse.getHead().setOrigInst(ouId);
|
|
78
|
+
fetchResponse.getTxn().setTs(fetchRequest.getTxn().getTs());
|
|
79
|
+
fetchResponse.getTxn().setMsgId(fetchRequest.getTxn().getMsgId());
|
|
80
|
+
fetchResponse.getTxn().setTxnReferenceId(fetchRequest.getTxn().getTxnReferenceId());
|
|
81
|
+
BillerResponseType billerResponseType = new BillerResponseType();
|
|
82
|
+
|
|
83
|
+
billerResponseType.setAmount(String.valueOf(bd.getBillAmount()));
|
|
84
|
+
billerResponseType.setBillDate(String.valueOf(bd.getBillDate()));
|
|
85
|
+
billerResponseType.setDueDate(String.valueOf(bd.getDueDate()));
|
|
86
|
+
// fetchResponse.getBillerResponses().set(0, billerResponseType);
|
|
87
|
+
fetchResponse.getBillerResponses().add(billerResponseType);
|
|
88
|
+
fetchResponse.setBillDetails(fetchRequest.getBillDetails());
|
|
89
|
+
ReasonType responseReason = new ReasonType();
|
|
90
|
+
responseReason.setResponseCode("000");
|
|
91
|
+
responseReason.setApprovalRefNum("AB123456");
|
|
92
|
+
responseReason.setResponseReason("Successful");
|
|
93
|
+
fetchResponse.setReason(responseReason);
|
|
94
|
+
fetchResponse.getTxn().setRiskScores(null);
|
|
95
|
+
|
|
96
|
+
Marshaller marshaller = jaxbContext.createMarshaller();
|
|
97
|
+
StringWriter sw = new StringWriter();
|
|
98
|
+
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
|
|
99
|
+
marshaller.marshal(fetchResponse, sw);
|
|
100
|
+
System.out.println("========================= BillFetchResponse SENT ================================");
|
|
101
|
+
System.out.println(sw.toString());
|
|
102
|
+
|
|
103
|
+
String bbpsBillFetchResponseUrl = bbpsIp + billFetchResponseUrl + fetchResponse.getHead().getRefId();
|
|
104
|
+
ResponseEntity<Ack> ackResponse = ouRestTemplate.postForEntity(
|
|
105
|
+
bbpsBillFetchResponseUrl,
|
|
106
|
+
fetchResponse,
|
|
107
|
+
Ack.class,
|
|
108
|
+
MediaType.APPLICATION_XML,
|
|
109
|
+
MediaType.APPLICATION_XML
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
System.out.println("==================== Received Ack against Bill Fetch Response ===============");
|
|
113
|
+
System.out.println(ackResponse.getStatusCode());
|
|
114
|
+
if (ackResponse.getBody() != null) {
|
|
115
|
+
JAXBContext ackContext = JAXBContext.newInstance(Ack.class);
|
|
116
|
+
Marshaller ackMarshaller = ackContext.createMarshaller();
|
|
117
|
+
ackMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
|
|
118
|
+
StringWriter ackSw = new StringWriter();
|
|
119
|
+
ackMarshaller.marshal(ackResponse.getBody(), ackSw);
|
|
120
|
+
System.out.println(ackSw);
|
|
121
|
+
} else {
|
|
122
|
+
System.out.println("Ack body is null");
|
|
123
|
+
}
|
|
124
|
+
} catch (JAXBException e1) {
|
|
125
|
+
e1.printStackTrace();
|
|
126
|
+
} catch (Exception e) {
|
|
127
|
+
e.printStackTrace();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
package bharat.connect.biller.service.impl;
|
|
2
|
+
|
|
3
|
+
import bharat.connect.biller.common.CommonUtils;
|
|
4
|
+
import bharat.connect.biller.model.BillDetails;
|
|
5
|
+
import bharat.connect.biller.provider.BillingProvider;
|
|
6
|
+
import bharat.connect.biller.provider.CustomerParamCriteriaMapper;
|
|
7
|
+
import bharat.connect.biller.provider.PaymentUpdateRequest;
|
|
8
|
+
import bharat.connect.biller.rest.OuRestTemplate;
|
|
9
|
+
import bharat.connect.biller.service.BillPaymentService;
|
|
10
|
+
import org.bbps.schema.*;
|
|
11
|
+
import org.springframework.beans.factory.annotation.Autowired;
|
|
12
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
13
|
+
import org.springframework.http.MediaType;
|
|
14
|
+
import org.springframework.http.ResponseEntity;
|
|
15
|
+
import org.springframework.scheduling.annotation.Async;
|
|
16
|
+
import org.springframework.stereotype.Service;
|
|
17
|
+
|
|
18
|
+
import jakarta.xml.bind.JAXBContext;
|
|
19
|
+
import jakarta.xml.bind.JAXBException;
|
|
20
|
+
import jakarta.xml.bind.Marshaller;
|
|
21
|
+
import java.io.StringWriter;
|
|
22
|
+
import java.math.BigDecimal;
|
|
23
|
+
|
|
24
|
+
@Service
|
|
25
|
+
public class BillPaymentServiceImpl implements BillPaymentService {
|
|
26
|
+
|
|
27
|
+
@Autowired
|
|
28
|
+
private BillingProvider billingProvider;
|
|
29
|
+
|
|
30
|
+
@Value("${ou.id}")
|
|
31
|
+
private String ouId;
|
|
32
|
+
|
|
33
|
+
@Value("${bou.domain}")
|
|
34
|
+
private String bouIp;
|
|
35
|
+
|
|
36
|
+
@Value("${bou.billpaymentresponse.url}")
|
|
37
|
+
private String billPaymentResponseUrl;
|
|
38
|
+
|
|
39
|
+
@Autowired
|
|
40
|
+
private final OuRestTemplate ouRestTemplate;
|
|
41
|
+
|
|
42
|
+
private static final JAXBContext jaxbContext;
|
|
43
|
+
|
|
44
|
+
static {
|
|
45
|
+
JAXBContext jaxbContext2;
|
|
46
|
+
try {
|
|
47
|
+
jaxbContext2 = JAXBContext.newInstance(Ack.class, BillPaymentResponse.class);
|
|
48
|
+
} catch (JAXBException e) {
|
|
49
|
+
e.printStackTrace();
|
|
50
|
+
jaxbContext2 = null;
|
|
51
|
+
}
|
|
52
|
+
jaxbContext = jaxbContext2;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public BillPaymentServiceImpl(OuRestTemplate ouRestTemplate) {
|
|
56
|
+
this.ouRestTemplate = ouRestTemplate;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Async
|
|
60
|
+
@Override
|
|
61
|
+
public void processBillPaymentAsync(BillPaymentRequest paymentRequest, String referenceId) {
|
|
62
|
+
System.out.println("Processing Bill Payment");
|
|
63
|
+
if (paymentRequest == null || paymentRequest.getBillDetails() == null || paymentRequest.getBillDetails().getCustomerParams() == null) {
|
|
64
|
+
System.out.println("BillPaymentRequest or CustomerParams is null, skipping.");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
CustomerParamsType customerParams = paymentRequest.getBillDetails().getCustomerParams();
|
|
69
|
+
|
|
70
|
+
// Read the unpaid bill first to get bill details for the response
|
|
71
|
+
var criteria = CustomerParamCriteriaMapper.from(customerParams);
|
|
72
|
+
BillDetails bd = billingProvider.findLatestUnpaidBill(criteria);
|
|
73
|
+
if (bd == null) {
|
|
74
|
+
System.out.println("No unpaid bill found for payment");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
String ts = CommonUtils.getFormattedCurrentTimestamp();
|
|
79
|
+
String approvalRefNum = "BP" + (System.currentTimeMillis() % 1_000_000_000L);
|
|
80
|
+
|
|
81
|
+
String bbpsTxnRef = paymentRequest.getHead().getRefId();
|
|
82
|
+
BigDecimal amountPaid = bd.getBillAmount();
|
|
83
|
+
String paymentMode = null;
|
|
84
|
+
if (paymentRequest.getPaymentMethod() != null) {
|
|
85
|
+
paymentMode = paymentRequest.getPaymentMethod().getPaymentMode();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Single @Transactional call: marks bill_details as PAID + inserts payment_transactions.
|
|
90
|
+
// Both succeed or both rollback.
|
|
91
|
+
boolean success = billingProvider.markBillPaidAndRecordTransaction(
|
|
92
|
+
criteria,
|
|
93
|
+
PaymentUpdateRequest.builder()
|
|
94
|
+
.billId(bd.getBillId())
|
|
95
|
+
.bbpsTxnRef(bbpsTxnRef)
|
|
96
|
+
.amountPaid(amountPaid)
|
|
97
|
+
.paymentMode(paymentMode)
|
|
98
|
+
.build()
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (!success) {
|
|
102
|
+
System.out.println("DB update failed — bill_status not updated or payment_transactions insert failed. Skipping response.");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
System.out.println("DB success: bill_id=" + bd.getBillId()
|
|
107
|
+
+ " marked PAID, payment_transactions row created (txnRef=" + bbpsTxnRef + ")");
|
|
108
|
+
|
|
109
|
+
// Both DB operations succeeded — build and send BillPaymentResponse
|
|
110
|
+
BillPaymentResponse resp = new BillPaymentResponse();
|
|
111
|
+
|
|
112
|
+
resp.setHead(paymentRequest.getHead());
|
|
113
|
+
resp.setTxn(paymentRequest.getTxn());
|
|
114
|
+
resp.setBillDetails(paymentRequest.getBillDetails());
|
|
115
|
+
|
|
116
|
+
resp.getHead().setRefId(paymentRequest.getHead().getRefId());
|
|
117
|
+
resp.getHead().setTs(ts);
|
|
118
|
+
resp.getHead().setOrigInst(ouId);
|
|
119
|
+
|
|
120
|
+
resp.getTxn().setTs(paymentRequest.getTxn().getTs());
|
|
121
|
+
resp.getTxn().setMsgId(paymentRequest.getTxn().getMsgId());
|
|
122
|
+
resp.getTxn().setTxnReferenceId(paymentRequest.getTxn().getTxnReferenceId());
|
|
123
|
+
resp.getTxn().setRiskScores(null);
|
|
124
|
+
|
|
125
|
+
ReasonType reason = new ReasonType();
|
|
126
|
+
reason.setResponseCode("000");
|
|
127
|
+
reason.setApprovalRefNum(approvalRefNum);
|
|
128
|
+
reason.setResponseReason("Successful");
|
|
129
|
+
resp.setReason(reason);
|
|
130
|
+
|
|
131
|
+
if (paymentRequest.getBillerResponses() != null && !paymentRequest.getBillerResponses().isEmpty()) {
|
|
132
|
+
resp.getBillerResponses().addAll(paymentRequest.getBillerResponses());
|
|
133
|
+
} else {
|
|
134
|
+
BillerResponseType br = new BillerResponseType();
|
|
135
|
+
br.setAmount(String.valueOf(bd.getBillAmount()));
|
|
136
|
+
br.setBillDate(String.valueOf(bd.getBillDate()));
|
|
137
|
+
br.setDueDate(String.valueOf(bd.getDueDate()));
|
|
138
|
+
resp.getBillerResponses().add(br);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Marshaller marshaller = jaxbContext.createMarshaller();
|
|
142
|
+
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
|
|
143
|
+
StringWriter sw = new StringWriter();
|
|
144
|
+
marshaller.marshal(resp, sw);
|
|
145
|
+
|
|
146
|
+
System.out.println("========================= BillPaymentResponse SENT ================================");
|
|
147
|
+
System.out.println(sw);
|
|
148
|
+
|
|
149
|
+
String bbpsBillPaymentResponseUrl = bouIp + billPaymentResponseUrl + resp.getHead().getRefId();
|
|
150
|
+
ResponseEntity<Ack> ackResponse = ouRestTemplate.postForEntity(
|
|
151
|
+
bbpsBillPaymentResponseUrl,
|
|
152
|
+
resp,
|
|
153
|
+
Ack.class,
|
|
154
|
+
MediaType.APPLICATION_XML,
|
|
155
|
+
MediaType.APPLICATION_XML
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
System.out.println("==================== Received Ack against Bill Payment Response ===============");
|
|
159
|
+
System.out.println(ackResponse.getStatusCode());
|
|
160
|
+
if (ackResponse.getBody() != null) {
|
|
161
|
+
JAXBContext ackContext = JAXBContext.newInstance(Ack.class);
|
|
162
|
+
Marshaller ackMarshaller = ackContext.createMarshaller();
|
|
163
|
+
ackMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
|
|
164
|
+
StringWriter ackSw = new StringWriter();
|
|
165
|
+
ackMarshaller.marshal(ackResponse.getBody(), ackSw);
|
|
166
|
+
System.out.println(ackSw);
|
|
167
|
+
} else {
|
|
168
|
+
System.out.println("Ack body is null");
|
|
169
|
+
}
|
|
170
|
+
} catch (Exception e) {
|
|
171
|
+
System.out.println("ERROR in processBillPaymentAsync for txnRef=" + bbpsTxnRef + ": " + e.getMessage());
|
|
172
|
+
e.printStackTrace();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
package bharat.connect.biller.service.impl;
|
|
2
|
+
|
|
3
|
+
import bharat.connect.biller.common.CommonUtils;
|
|
4
|
+
import bharat.connect.biller.rest.OuRestTemplate;
|
|
5
|
+
import bharat.connect.biller.service.HeartbeatService;
|
|
6
|
+
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
7
|
+
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
8
|
+
import org.apache.commons.lang3.RandomStringUtils;
|
|
9
|
+
import org.bbps.schema.HeadType;
|
|
10
|
+
import org.bbps.schema.ReqDiagnostic;
|
|
11
|
+
import org.springframework.beans.factory.annotation.Autowired;
|
|
12
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
13
|
+
import org.springframework.http.MediaType;
|
|
14
|
+
import org.springframework.http.ResponseEntity;
|
|
15
|
+
import org.springframework.stereotype.Service;
|
|
16
|
+
import org.springframework.util.StringUtils;
|
|
17
|
+
|
|
18
|
+
import jakarta.xml.bind.JAXBContext;
|
|
19
|
+
import jakarta.xml.bind.Marshaller;
|
|
20
|
+
import java.security.SecureRandom;
|
|
21
|
+
import java.time.LocalDate;
|
|
22
|
+
import java.time.LocalDateTime;
|
|
23
|
+
import java.time.format.DateTimeFormatter;
|
|
24
|
+
|
|
25
|
+
@Service
|
|
26
|
+
public class HeartbeatServiceImpl implements HeartbeatService {
|
|
27
|
+
|
|
28
|
+
@Autowired
|
|
29
|
+
private OuRestTemplate restTemplate;
|
|
30
|
+
|
|
31
|
+
@Value("${diagnosticReqFormat:application/xml}")
|
|
32
|
+
private String contentType;
|
|
33
|
+
|
|
34
|
+
@Value("${julianDt.refId:YES}")
|
|
35
|
+
private String julianDtRefId;
|
|
36
|
+
|
|
37
|
+
@Value("${julianDateDelay:0}")
|
|
38
|
+
private int julianDateDelay;
|
|
39
|
+
|
|
40
|
+
@Value("${hbt.frequency:1}")
|
|
41
|
+
private String hbtFreq;
|
|
42
|
+
|
|
43
|
+
@Value("${bou.domain:${bou.domain}}")
|
|
44
|
+
private String bouDomain;
|
|
45
|
+
|
|
46
|
+
@Value("${ou.id}")
|
|
47
|
+
private String ouId;
|
|
48
|
+
|
|
49
|
+
public static final String BOU_DIAGNOSTIC_REQUEST_URL = "/bbps/ReqHbt/1.0/urn:referenceId:";
|
|
50
|
+
|
|
51
|
+
@Override
|
|
52
|
+
public String sendHeartbeat() {
|
|
53
|
+
ResponseEntity<String> resp = null;
|
|
54
|
+
ReqDiagnostic reqHbt = new ReqDiagnostic();
|
|
55
|
+
ReqDiagnostic reqHbtJson = new ReqDiagnostic();
|
|
56
|
+
String headRefId = "";
|
|
57
|
+
String mediaType = contentType.equalsIgnoreCase("JSON") ? MediaType.APPLICATION_JSON_VALUE : MediaType.APPLICATION_XML_VALUE;
|
|
58
|
+
if (julianDtRefId.equalsIgnoreCase("YES"))
|
|
59
|
+
headRefId = generateJulianDtId();
|
|
60
|
+
else
|
|
61
|
+
headRefId = StringUtils.capitalize(RandomStringUtils.randomAlphanumeric(35));
|
|
62
|
+
|
|
63
|
+
System.out.println("=====hbt=====" + ouId);
|
|
64
|
+
String bouDiagnosticUrlUrl = bouDomain + BOU_DIAGNOSTIC_REQUEST_URL + headRefId;
|
|
65
|
+
System.out.println("=====bouDiagnosticUrlUrl=====>>" + bouDiagnosticUrlUrl);
|
|
66
|
+
if (mediaType.equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
|
|
67
|
+
|
|
68
|
+
HeadType head = new HeadType();
|
|
69
|
+
head.setTs(CommonUtils.getFormattedCurrentTimestamp());
|
|
70
|
+
head.setOrigInst(ouId);
|
|
71
|
+
head.setRefId(headRefId);
|
|
72
|
+
head.setVer("1.0");
|
|
73
|
+
reqHbtJson.setHead(head);
|
|
74
|
+
ObjectMapper mapper = new ObjectMapper();
|
|
75
|
+
try {
|
|
76
|
+
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(reqHbtJson));
|
|
77
|
+
|
|
78
|
+
} catch (JsonProcessingException e) {
|
|
79
|
+
throw new RuntimeException(e);
|
|
80
|
+
}
|
|
81
|
+
resp = restTemplate.postForEntity(bouDiagnosticUrlUrl, reqHbtJson, String.class,
|
|
82
|
+
MediaType.valueOf(mediaType), MediaType.valueOf(mediaType));
|
|
83
|
+
|
|
84
|
+
} else {
|
|
85
|
+
try {
|
|
86
|
+
org.bbps.schema.HeadType head = new org.bbps.schema.HeadType();
|
|
87
|
+
head.setTs(CommonUtils.getFormattedCurrentTimestamp());
|
|
88
|
+
head.setOrigInst(ouId);
|
|
89
|
+
head.setRefId(headRefId);
|
|
90
|
+
head.setVer("1.0");
|
|
91
|
+
reqHbt.setHead(head);
|
|
92
|
+
JAXBContext jaxbContext = JAXBContext.newInstance(ReqDiagnostic.class);
|
|
93
|
+
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
|
|
94
|
+
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
|
|
95
|
+
jaxbMarshaller.marshal(reqHbt, System.out);
|
|
96
|
+
} catch (Exception e) {
|
|
97
|
+
e.printStackTrace();
|
|
98
|
+
}
|
|
99
|
+
resp = restTemplate.postForEntity(bouDiagnosticUrlUrl, reqHbt, String.class,
|
|
100
|
+
MediaType.valueOf(mediaType), MediaType.valueOf(mediaType));
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
if (MediaType.APPLICATION_JSON_VALUE.equals(mediaType)) {
|
|
105
|
+
System.out.println("ApplicationContext.HeartBeatGenerator.run() response Json:" + new ObjectMapper().readTree(resp.getBody()).toPrettyString());
|
|
106
|
+
} else {
|
|
107
|
+
System.out.println("ApplicationContext.HeartBeatGenerator.run() response:" + resp.getBody());
|
|
108
|
+
}
|
|
109
|
+
} catch (Exception e) {
|
|
110
|
+
e.printStackTrace();
|
|
111
|
+
}
|
|
112
|
+
return resp.getBody();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private String generateJulianDtId() {
|
|
116
|
+
char[] chars = "abcdefghijklmnopqrstuvwxyz1234567890".toCharArray();
|
|
117
|
+
StringBuilder sb = new StringBuilder();
|
|
118
|
+
//Random random = new Random();
|
|
119
|
+
SecureRandom random = new SecureRandom();
|
|
120
|
+
|
|
121
|
+
for (int i = 0; i < 27; i++) {
|
|
122
|
+
char c = chars[random.nextInt(27)];
|
|
123
|
+
sb.append(c);
|
|
124
|
+
}
|
|
125
|
+
if (julianDateDelay > 0)
|
|
126
|
+
sb.append(LocalDate.now().plusDays(julianDateDelay).format(DateTimeFormatter.ofPattern("yDDD")).substring(3));
|
|
127
|
+
else if (julianDateDelay < 0)
|
|
128
|
+
sb.append(LocalDate.now().minusDays(Math.abs(julianDateDelay)).format(DateTimeFormatter.ofPattern("yDDD")).substring(3));
|
|
129
|
+
else if (julianDateDelay == 0)
|
|
130
|
+
sb.append(LocalDate.now().format(DateTimeFormatter.ofPattern("YDDD")).substring(3));
|
|
131
|
+
|
|
132
|
+
sb.append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HHmm")));
|
|
133
|
+
String output = sb.toString();
|
|
134
|
+
return output.toUpperCase();
|
|
135
|
+
}
|
|
136
|
+
}
|