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.
Files changed (33) hide show
  1. biller_cli/__init__.py +0 -0
  2. biller_cli/ai/__init__.py +397 -0
  3. biller_cli/ai/ingest.py +394 -0
  4. biller_cli/commands/down.py +96 -0
  5. biller_cli/commands/downgrade.py +142 -0
  6. biller_cli/commands/init.py +210 -0
  7. biller_cli/commands/up.py +138 -0
  8. biller_cli/commands/upgrade.py +271 -0
  9. biller_cli/main.py +34 -0
  10. biller_cli/templates/biller-user-layer/pom.xml +100 -0
  11. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/ApplicationContext.java +13 -0
  12. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/config/DatabaseConfig.java +35 -0
  13. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/config/WebCorsConfig.java +21 -0
  14. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/controller/BbpsRequestController.java +98 -0
  15. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/dao/impl/BillFetchDaoImpl.java +135 -0
  16. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/dao/impl/BillPaymentDaoImpl.java +100 -0
  17. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/BillingProviderConfig.java +34 -0
  18. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/BillingProviderProperties.java +92 -0
  19. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/impl/CsvBillingProvider.java +257 -0
  20. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/impl/ExcelBillingProvider.java +261 -0
  21. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/provider/impl/PostgresBillingProvider.java +210 -0
  22. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/service/impl/BillFetchServiceImpl.java +131 -0
  23. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/service/impl/BillPaymentServiceImpl.java +175 -0
  24. biller_cli/templates/biller-user-layer/src/main/java/bharat/connect/biller/service/impl/HeartbeatServiceImpl.java +136 -0
  25. biller_cli/templates/biller-user-layer/src/main/resources/application.properties +88 -0
  26. biller_cli/templates/pom.xml +33 -0
  27. biller_cli/utils/preflight.py +151 -0
  28. biller_cli/utils/secrets.py +37 -0
  29. biller_cli/utils/version_pin.py +67 -0
  30. biller_cli-0.1.0.dist-info/METADATA +17 -0
  31. biller_cli-0.1.0.dist-info/RECORD +33 -0
  32. biller_cli-0.1.0.dist-info/WHEEL +4 -0
  33. biller_cli-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,100 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project xmlns="http://maven.apache.org/POM/4.0.0"
3
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
+ <modelVersion>4.0.0</modelVersion>
6
+ <parent>
7
+ <groupId>com.bharatconnect.biller</groupId>
8
+ <artifactId>test-biller-integrator</artifactId>
9
+ <version>1.0.0</version>
10
+ </parent>
11
+ <artifactId>biller-user-layer</artifactId>
12
+ <version>1.0.0</version>
13
+ <packaging>jar</packaging>
14
+ <description>BBPS Biller User Layer</description>
15
+ <properties>
16
+ <biller.core.version>{{BILLER_CORE_VERSION}}</biller.core.version>
17
+ </properties>
18
+ <dependencies>
19
+ <dependency>
20
+ <groupId>com.bharatconnect.biller</groupId>
21
+ <artifactId>biller-core</artifactId>
22
+ <version>${biller.core.version}</version>
23
+ </dependency>
24
+ <dependency>
25
+ <groupId>org.springframework.boot</groupId>
26
+ <artifactId>spring-boot-starter-web</artifactId>
27
+ </dependency>
28
+ <dependency>
29
+ <groupId>org.springframework.boot</groupId>
30
+ <artifactId>spring-boot-starter-data-jpa</artifactId>
31
+ </dependency>
32
+ <dependency>
33
+ <groupId>org.springframework.boot</groupId>
34
+ <artifactId>spring-boot-starter-jdbc</artifactId>
35
+ </dependency>
36
+ <dependency>
37
+ <groupId>org.postgresql</groupId>
38
+ <artifactId>postgresql</artifactId>
39
+ <scope>runtime</scope>
40
+ </dependency>
41
+ <dependency>
42
+ <groupId>org.apache.poi</groupId>
43
+ <artifactId>poi-ooxml</artifactId>
44
+ <version>5.2.5</version>
45
+ </dependency>
46
+ <dependency>
47
+ <groupId>org.apache.commons</groupId>
48
+ <artifactId>commons-lang3</artifactId>
49
+ </dependency>
50
+ <dependency>
51
+ <groupId>jakarta.xml.bind</groupId>
52
+ <artifactId>jakarta.xml.bind-api</artifactId>
53
+ </dependency>
54
+ <dependency>
55
+ <groupId>com.sun.xml.bind</groupId>
56
+ <artifactId>jaxb-impl</artifactId>
57
+ <version>4.0.4</version>
58
+ </dependency>
59
+ <dependency>
60
+ <groupId>org.projectlombok</groupId>
61
+ <artifactId>lombok</artifactId>
62
+ <optional>true</optional>
63
+ </dependency>
64
+ <dependency>
65
+ <groupId>org.springframework.boot</groupId>
66
+ <artifactId>spring-boot-starter-test</artifactId>
67
+ <scope>test</scope>
68
+ </dependency>
69
+ </dependencies>
70
+ <build>
71
+ <plugins>
72
+ <plugin>
73
+ <groupId>org.apache.maven.plugins</groupId>
74
+ <artifactId>maven-compiler-plugin</artifactId>
75
+ <configuration>
76
+ <source>17</source>
77
+ <target>17</target>
78
+ <annotationProcessorPaths>
79
+ <path>
80
+ <groupId>org.projectlombok</groupId>
81
+ <artifactId>lombok</artifactId>
82
+ <version>1.18.32</version>
83
+ </path>
84
+ </annotationProcessorPaths>
85
+ </configuration>
86
+ </plugin>
87
+ <plugin>
88
+ <groupId>org.springframework.boot</groupId>
89
+ <artifactId>spring-boot-maven-plugin</artifactId>
90
+ <executions>
91
+ <execution>
92
+ <goals>
93
+ <goal>repackage</goal>
94
+ </goals>
95
+ </execution>
96
+ </executions>
97
+ </plugin>
98
+ </plugins>
99
+ </build>
100
+ </project>
@@ -0,0 +1,13 @@
1
+ package bharat.connect.biller;
2
+
3
+ import org.springframework.boot.SpringApplication;
4
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
5
+ import org.springframework.scheduling.annotation.EnableAsync;
6
+
7
+ @EnableAsync
8
+ @SpringBootApplication(scanBasePackages = "bharat.connect.biller")
9
+ public class ApplicationContext {
10
+ public static void main(String[] args) {
11
+ SpringApplication.run(ApplicationContext.class, args);
12
+ }
13
+ }
@@ -0,0 +1,35 @@
1
+ package bharat.connect.biller.config;
2
+
3
+ import org.springframework.beans.factory.annotation.Autowired;
4
+ import org.springframework.beans.factory.annotation.Qualifier;
5
+ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
6
+ import org.springframework.context.annotation.Bean;
7
+ import org.springframework.context.annotation.Configuration;
8
+ import org.springframework.core.env.Environment;
9
+ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
10
+ import org.springframework.jdbc.datasource.DriverManagerDataSource;
11
+
12
+ import javax.sql.DataSource;
13
+
14
+ @Configuration
15
+ @ConditionalOnProperty(prefix = "billing.provider", name = "type", havingValue = "postgres", matchIfMissing = true)
16
+ public class DatabaseConfig {
17
+
18
+ @Autowired
19
+ private Environment env;
20
+
21
+ @Bean
22
+ public DataSource dataSource() {
23
+ DriverManagerDataSource dataSource = new DriverManagerDataSource();
24
+ dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
25
+ dataSource.setUrl(env.getProperty("spring.datasource.url"));
26
+ dataSource.setUsername(env.getProperty("spring.datasource.username"));
27
+ dataSource.setPassword(env.getProperty("spring.datasource.password"));
28
+ return dataSource;
29
+ }
30
+
31
+ @Bean("jdbcNamedTemplate")
32
+ public NamedParameterJdbcTemplate jdbcNTemplate1(@Qualifier("dataSource") DataSource ds) {
33
+ return new NamedParameterJdbcTemplate(ds);
34
+ }
35
+ }
@@ -0,0 +1,21 @@
1
+ package bharat.connect.biller.config;
2
+
3
+ import org.springframework.beans.factory.annotation.Value;
4
+ import org.springframework.context.annotation.Configuration;
5
+ import org.springframework.web.servlet.config.annotation.CorsRegistry;
6
+ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
7
+
8
+ @Configuration
9
+ public class WebCorsConfig implements WebMvcConfigurer {
10
+
11
+ @Value("${app.cors.allowed-origins:http://localhost:3000}")
12
+ private String[] allowedOrigins;
13
+
14
+ @Override
15
+ public void addCorsMappings(CorsRegistry registry) {
16
+ registry.addMapping("/**")
17
+ .allowedOrigins(allowedOrigins)
18
+ .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
19
+ .allowedHeaders("*");
20
+ }
21
+ }
@@ -0,0 +1,98 @@
1
+ package bharat.connect.biller.controller;
2
+
3
+ import bharat.connect.biller.common.API;
4
+ import bharat.connect.biller.common.CommonConstants;
5
+ import bharat.connect.biller.common.CommonUtils;
6
+ import bharat.connect.biller.service.BillPaymentService;
7
+ import bharat.connect.biller.service.BillFetchService;
8
+ import bharat.connect.biller.service.HeartbeatService;
9
+ import org.bbps.schema.Ack;
10
+ import org.bbps.schema.BillFetchRequest;
11
+ import org.bbps.schema.BillPaymentRequest;
12
+ import org.springframework.beans.factory.annotation.Autowired;
13
+ import org.springframework.http.HttpStatus;
14
+ import org.springframework.http.MediaType;
15
+ import org.springframework.http.ResponseEntity;
16
+ import org.springframework.web.bind.annotation.*;
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
+
23
+ @RestController
24
+ @RequestMapping("/")
25
+ public class BbpsRequestController {
26
+
27
+ @Autowired
28
+ private BillFetchService billFetchService;
29
+
30
+ @Autowired
31
+ private BillPaymentService billPaymentService;
32
+
33
+ @Autowired
34
+ private HeartbeatService heartbeatService;
35
+
36
+ @RequestMapping(value = "/BillFetchRequest/1.0/urn:referenceId:{referenceId}", method = RequestMethod.POST, produces = MediaType.APPLICATION_XML_VALUE)
37
+ public Ack billFetchRequest(@RequestBody BillFetchRequest fetchRequest, @PathVariable("referenceId") String referenceId) {
38
+ System.out.println("====================received bill fetch request===============");
39
+ printXml(fetchRequest, BillFetchRequest.class, "BillFetchRequest XML");
40
+ if(fetchRequest != null && fetchRequest.getHead().getRefId()!=null && !fetchRequest.getHead().getRefId().isEmpty()){
41
+ billFetchService.processBillFetchAsync(fetchRequest,fetchRequest.getHead().getRefId());
42
+ }
43
+ fetchRequest.getBillDetails().getCustomerParams().getTags().stream().forEach(a -> System.out.println(a.getName() +" - "+a.getValue()));
44
+
45
+ Ack ack = new Ack();
46
+ API action = API.FETCH_REQUEST;
47
+ ack.setRefId(fetchRequest.getHead().getRefId());
48
+ ack.setApi(action.name());
49
+ ack.setTs(CommonUtils.getFormattedCurrentTimestamp());
50
+ ack.setRspCd(CommonConstants.RESP_SUCCESS_MSG);
51
+ printXml(ack, Ack.class, "BillFetchRequest ACK");
52
+ return ack;
53
+ }
54
+
55
+ @RequestMapping(value = "/BillPaymentRequest/1.0/urn:referenceId:{referenceId}", method = RequestMethod.POST, produces = MediaType.APPLICATION_XML_VALUE)
56
+ public Ack billPaymentRequest(@RequestBody BillPaymentRequest paymentRequest, @PathVariable("referenceId") String referenceId) {
57
+ System.out.println("====================received bill payment request===============");
58
+ printXml(paymentRequest, BillPaymentRequest.class, "BillPaymentRequest XML");
59
+
60
+ if (paymentRequest != null && paymentRequest.getHead() != null && paymentRequest.getHead().getRefId() != null && !paymentRequest.getHead().getRefId().isEmpty()) {
61
+ billPaymentService.processBillPaymentAsync(paymentRequest, paymentRequest.getHead().getRefId());
62
+ }
63
+
64
+ Ack ack = new Ack();
65
+ API action = API.PAYMENT_REQUEST;
66
+ ack.setRefId(paymentRequest.getHead().getRefId());
67
+ ack.setApi(action.name());
68
+ ack.setTs(CommonUtils.getFormattedCurrentTimestamp());
69
+ ack.setRspCd(CommonConstants.RESP_SUCCESS_MSG);
70
+ printXml(ack, Ack.class, "BillPaymentRequest ACK");
71
+ return ack;
72
+ }
73
+
74
+ @RequestMapping(value = "/heartbeat", method = RequestMethod.GET)
75
+ public String billFetchRequest() {
76
+ return heartbeatService.sendHeartbeat();
77
+ }
78
+
79
+ @RequestMapping(value = "/health", method = RequestMethod.GET)
80
+ public ResponseEntity<?> checkHealthStatus() {
81
+ return new ResponseEntity<>(HttpStatus.OK);
82
+ }
83
+
84
+ private void printXml(Object payload, Class<?> payloadClass, String label) {
85
+ try {
86
+ JAXBContext context = JAXBContext.newInstance(payloadClass);
87
+ Marshaller marshaller = context.createMarshaller();
88
+ marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
89
+ StringWriter sw = new StringWriter();
90
+ marshaller.marshal(payload, sw);
91
+ System.out.println("=============== " + label + " ===============");
92
+ System.out.println(sw);
93
+ } catch (JAXBException e) {
94
+ e.printStackTrace();
95
+ }
96
+ }
97
+
98
+ }
@@ -0,0 +1,135 @@
1
+ package bharat.connect.biller.dao.impl;
2
+
3
+ import bharat.connect.biller.dao.BillFetchDao;
4
+ import bharat.connect.biller.model.BillDetails;
5
+ import org.bbps.schema.CustomerParamsType;
6
+ import org.springframework.beans.factory.annotation.Autowired;
7
+ import org.springframework.beans.factory.annotation.Qualifier;
8
+ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
9
+ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
10
+ import org.springframework.stereotype.Repository;
11
+
12
+ import java.util.List;
13
+
14
+ @Repository
15
+ public class BillFetchDaoImpl implements BillFetchDao {
16
+
17
+ @Autowired
18
+ @Qualifier(value = "jdbcNamedTemplate")
19
+ private NamedParameterJdbcTemplate jdbcNamedTemplate;
20
+
21
+ @Override
22
+ public BillDetails findLatestUnpaidByParam(String paramName, String paramValue) {
23
+ String sql = """
24
+ SELECT bill_id, customer_param_name, customer_param_type, customer_param_value,
25
+ bill_amount, bill_date, due_date, bill_number, bill_period, bill_status,
26
+ additional_info, created_at, updated_at
27
+ FROM bill_details
28
+ WHERE customer_param_name = :paramName
29
+ AND customer_param_value = :paramValue
30
+ AND bill_status = 'UNPAID'
31
+ ORDER BY due_date NULLS LAST, bill_date DESC, bill_id DESC
32
+ LIMIT 1
33
+ """;
34
+
35
+ MapSqlParameterSource params = new MapSqlParameterSource().addValue("paramName", paramName).addValue("paramValue", paramValue);
36
+
37
+ List<BillDetails> list = jdbcNamedTemplate.query(sql, params, (rs, rowNum) -> {
38
+ BillDetails b = new BillDetails();
39
+ b.setBillId((long) rs.getInt("bill_id"));
40
+ b.setCustomerParamName(rs.getString("customer_param_name"));
41
+ b.setCustomerParamType(rs.getString("customer_param_type"));
42
+ b.setCustomerParamValue(rs.getString("customer_param_value"));
43
+ b.setBillAmount(rs.getBigDecimal("bill_amount"));
44
+ var billDate = rs.getDate("bill_date");
45
+ if (billDate != null) b.setBillDate(billDate.toLocalDate());
46
+ var dueDate = rs.getDate("due_date");
47
+ if (dueDate != null) b.setDueDate(dueDate.toLocalDate());
48
+ b.setBillNumber(rs.getString("bill_number"));
49
+ b.setBillPeriod(rs.getString("bill_period"));
50
+ b.setBillStatus(rs.getString("bill_status"));
51
+ b.setAdditionalInfo(rs.getString("additional_info"));
52
+ var created = rs.getTimestamp("created_at");
53
+ if (created != null) b.setCreatedAt(created.toLocalDateTime());
54
+ var updated = rs.getTimestamp("updated_at");
55
+ if (updated != null) b.setUpdatedAt(updated.toLocalDateTime());
56
+ return b;
57
+ });
58
+ if (list.isEmpty()) return null;
59
+ return list.get(0);
60
+ }
61
+ @Override
62
+ public BillDetails findLatestUnpaidBillByCustomerParams(CustomerParamsType customerParamsType) {
63
+ List<CustomerParamsType.Tag> tags = customerParamsType.getTags();
64
+ if (tags == null || tags.isEmpty()) {
65
+ return null;
66
+ }
67
+ StringBuilder sql = new StringBuilder("""
68
+ SELECT bill_id, customer_param_name, customer_param_type, customer_param_value,
69
+ bill_amount, bill_date, due_date, bill_number, bill_period, bill_status,
70
+ additional_info, created_at, updated_at
71
+ FROM bill_details
72
+ WHERE bill_status = 'UNPAID'
73
+ """);
74
+
75
+ MapSqlParameterSource params = new MapSqlParameterSource();
76
+
77
+ // Build the OR group: ( (name=:n0 AND value=:v0) OR (name=:n1 AND value=:v1) OR ... )
78
+ boolean addedAny = false;
79
+ for (int i = 0; i < tags.size(); i++) {
80
+ CustomerParamsType.Tag t = tags.get(i);
81
+ if (t == null) continue;
82
+ String name = (t.getName() == null) ? null : t.getName().trim();
83
+ String value = (t.getValue() == null) ? null : t.getValue().trim();
84
+ if (name == null || name.isEmpty() || value == null || value.isEmpty()) continue;
85
+
86
+ String nKey = "name" + i;
87
+ String vKey = "value" + i;
88
+ params.addValue(nKey, name);
89
+ params.addValue(vKey, value);
90
+
91
+ sql.append(addedAny ? " OR " : " AND (");
92
+ sql.append("(customer_param_name = :").append(nKey).append(" AND customer_param_value = :").append(vKey).append(")");
93
+ addedAny = true;
94
+ }
95
+
96
+ if (addedAny) {
97
+ sql.append(")");
98
+ } else {
99
+ // All tags were blank/null — nothing meaningful to filter on
100
+ return null;
101
+ }
102
+
103
+ sql.append("""
104
+ ORDER BY due_date NULLS LAST, bill_date DESC, bill_id DESC
105
+ LIMIT 1
106
+ """);
107
+
108
+ List<BillDetails> list = jdbcNamedTemplate.query(sql.toString(), params, (rs, rowNum) -> {
109
+ BillDetails b = new BillDetails();
110
+ b.setBillId((long) rs.getInt("bill_id"));
111
+ b.setCustomerParamName(rs.getString("customer_param_name"));
112
+ b.setCustomerParamType(rs.getString("customer_param_type"));
113
+ b.setCustomerParamValue(rs.getString("customer_param_value"));
114
+ b.setBillAmount(rs.getBigDecimal("bill_amount"));
115
+
116
+ var billDate = rs.getDate("bill_date");
117
+ if (billDate != null) b.setBillDate(billDate.toLocalDate());
118
+ var dueDate = rs.getDate("due_date");
119
+ if (dueDate != null) b.setDueDate(dueDate.toLocalDate());
120
+
121
+ b.setBillNumber(rs.getString("bill_number"));
122
+ b.setBillPeriod(rs.getString("bill_period"));
123
+ b.setBillStatus(rs.getString("bill_status"));
124
+ b.setAdditionalInfo(rs.getString("additional_info"));
125
+
126
+ var created = rs.getTimestamp("created_at");
127
+ if (created != null) b.setCreatedAt(created.toLocalDateTime());
128
+ var updated = rs.getTimestamp("updated_at");
129
+ if (updated != null) b.setUpdatedAt(updated.toLocalDateTime());
130
+
131
+ return b;
132
+ });
133
+ return list.isEmpty() ? null : list.get(0);
134
+ }
135
+ }
@@ -0,0 +1,100 @@
1
+ package bharat.connect.biller.dao.impl;
2
+
3
+ import bharat.connect.biller.dao.BillPaymentDao;
4
+ import org.bbps.schema.CustomerParamsType;
5
+ import org.springframework.beans.factory.annotation.Autowired;
6
+ import org.springframework.beans.factory.annotation.Qualifier;
7
+ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
8
+ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
9
+ import org.springframework.stereotype.Repository;
10
+ import org.springframework.transaction.annotation.Transactional;
11
+
12
+ import java.math.BigDecimal;
13
+ import java.util.List;
14
+
15
+ @Repository
16
+ public class BillPaymentDaoImpl implements BillPaymentDao {
17
+
18
+ @Autowired
19
+ @Qualifier(value = "jdbcNamedTemplate")
20
+ private NamedParameterJdbcTemplate jdbcNamedTemplate;
21
+
22
+ @Transactional
23
+ @Override
24
+ public boolean markBillPaidAndRecordTransaction(CustomerParamsType customerParams,
25
+ long billId,
26
+ String bbpsTxnRef,
27
+ BigDecimal amountPaid,
28
+ String paymentMode) {
29
+ List<CustomerParamsType.Tag> tags = customerParams.getTags();
30
+ if (tags == null || tags.isEmpty()) {
31
+ return false;
32
+ }
33
+
34
+ MapSqlParameterSource updateParams = new MapSqlParameterSource();
35
+
36
+ // Build customer-params matching clause (same pattern as BillFetchDaoImpl)
37
+ StringBuilder subWhere = new StringBuilder("bill_status = 'UNPAID'");
38
+ boolean addedAny = false;
39
+ for (int i = 0; i < tags.size(); i++) {
40
+ CustomerParamsType.Tag t = tags.get(i);
41
+ if (t == null) continue;
42
+ String name = (t.getName() == null) ? null : t.getName().trim();
43
+ String value = (t.getValue() == null) ? null : t.getValue().trim();
44
+ if (name == null || name.isEmpty() || value == null || value.isEmpty()) continue;
45
+
46
+ String nKey = "name" + i;
47
+ String vKey = "value" + i;
48
+ updateParams.addValue(nKey, name);
49
+ updateParams.addValue(vKey, value);
50
+
51
+ subWhere.append(addedAny ? " OR " : " AND (");
52
+ subWhere.append("(customer_param_name = :").append(nKey)
53
+ .append(" AND customer_param_value = :").append(vKey).append(")");
54
+ addedAny = true;
55
+ }
56
+
57
+ if (!addedAny) {
58
+ return false;
59
+ }
60
+ subWhere.append(")");
61
+
62
+ // Step 1: UPDATE bill_details to PAID, searched by customer params
63
+ String updateSql = """
64
+ UPDATE bill_details
65
+ SET bill_status = 'PAID',
66
+ updated_at = now()
67
+ WHERE bill_id = (
68
+ SELECT bill_id FROM bill_details
69
+ WHERE %s
70
+ ORDER BY due_date NULLS LAST, bill_date DESC, bill_id DESC
71
+ LIMIT 1
72
+ )
73
+ """.formatted(subWhere);
74
+
75
+ int updatedRows = jdbcNamedTemplate.update(updateSql, updateParams);
76
+ if (updatedRows <= 0) {
77
+ System.out.println("markBillPaidAndRecordTransaction: UPDATE matched 0 rows. No UNPAID bill for given customer params.");
78
+ return false;
79
+ }
80
+ System.out.println("markBillPaidAndRecordTransaction: bill_details updated to PAID (" + updatedRows + " row(s)).");
81
+
82
+ // Step 2: INSERT into payment_transactions (same transaction — rolls back UPDATE if this fails)
83
+ String insertSql = """
84
+ INSERT INTO payment_transactions (bill_id, bbps_txn_ref, amount_paid, payment_mode)
85
+ VALUES (:billId, :bbpsTxnRef, :amountPaid, :paymentMode)
86
+ """;
87
+
88
+ MapSqlParameterSource insertParams = new MapSqlParameterSource()
89
+ .addValue("billId", billId)
90
+ .addValue("bbpsTxnRef", bbpsTxnRef)
91
+ .addValue("amountPaid", amountPaid)
92
+ .addValue("paymentMode", paymentMode);
93
+
94
+ int inserted = jdbcNamedTemplate.update(insertSql, insertParams);
95
+ System.out.println("markBillPaidAndRecordTransaction: payment_transactions inserted=" + inserted
96
+ + " for bill_id=" + billId + ", bbpsTxnRef=" + bbpsTxnRef);
97
+
98
+ return updatedRows > 0 && inserted > 0;
99
+ }
100
+ }
@@ -0,0 +1,34 @@
1
+ package bharat.connect.biller.provider;
2
+
3
+ import bharat.connect.biller.provider.impl.CsvBillingProvider;
4
+ import bharat.connect.biller.provider.impl.ExcelBillingProvider;
5
+ import bharat.connect.biller.provider.impl.PostgresBillingProvider;
6
+ import org.springframework.boot.context.properties.EnableConfigurationProperties;
7
+ import org.springframework.beans.factory.ObjectProvider;
8
+ import org.springframework.context.annotation.Bean;
9
+ import org.springframework.context.annotation.Configuration;
10
+ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
11
+
12
+ @Configuration
13
+ @EnableConfigurationProperties(BillingProviderProperties.class)
14
+ public class BillingProviderConfig {
15
+
16
+ @Bean
17
+ public BillingProvider billingProvider(BillingProviderProperties properties,
18
+ ObjectProvider<NamedParameterJdbcTemplate> jdbcNamedTemplateProvider) {
19
+ String type = properties.getType() == null ? "postgres" : properties.getType().trim().toLowerCase();
20
+ return switch (type) {
21
+ case "csv" -> new CsvBillingProvider(properties);
22
+ case "excel" -> new ExcelBillingProvider(properties);
23
+ case "postgres" -> new PostgresBillingProvider(requireJdbcTemplate(jdbcNamedTemplateProvider.getIfAvailable()), properties);
24
+ default -> throw new IllegalArgumentException("Unsupported billing.provider.type: " + type);
25
+ };
26
+ }
27
+
28
+ private NamedParameterJdbcTemplate requireJdbcTemplate(NamedParameterJdbcTemplate jdbcNamedTemplate) {
29
+ if (jdbcNamedTemplate == null) {
30
+ throw new IllegalStateException("NamedParameterJdbcTemplate bean missing for postgres provider.");
31
+ }
32
+ return jdbcNamedTemplate;
33
+ }
34
+ }
@@ -0,0 +1,92 @@
1
+ package bharat.connect.biller.provider;
2
+
3
+ import lombok.Getter;
4
+ import lombok.Setter;
5
+ import org.springframework.boot.context.properties.ConfigurationProperties;
6
+
7
+ @Getter
8
+ @Setter
9
+ @ConfigurationProperties(prefix = "billing.provider")
10
+ public class BillingProviderProperties {
11
+ private String type = "postgres";
12
+ private String dateFormat = "yyyy-MM-dd";
13
+
14
+ private Csv csv = new Csv();
15
+ private Excel excel = new Excel();
16
+ private Postgres postgres = new Postgres();
17
+
18
+ @Getter
19
+ @Setter
20
+ public static class Csv {
21
+ private String path;
22
+ private String paymentLogPath;
23
+ private String delimiter = ",";
24
+ private Columns columns = new Columns();
25
+ }
26
+
27
+ @Getter
28
+ @Setter
29
+ public static class Excel {
30
+ private String path;
31
+ private String sheetName = "Sheet1";
32
+ private String paymentLogPath;
33
+ private Columns columns = new Columns();
34
+ }
35
+
36
+ @Getter
37
+ @Setter
38
+ public static class Columns {
39
+ private String billId = "bill_id";
40
+ private String customerParamName = "customer_param_name";
41
+ private String customerParamValue = "customer_param_value";
42
+ private String customerParamType = "customer_param_type";
43
+ private String billAmount = "bill_amount";
44
+ private String billDate = "bill_date";
45
+ private String dueDate = "due_date";
46
+ private String billNumber = "bill_number";
47
+ private String billPeriod = "bill_period";
48
+ private String billStatus = "bill_status";
49
+ }
50
+
51
+ @Getter
52
+ @Setter
53
+ public static class Postgres {
54
+ private Tables tables = new Tables();
55
+ private BillColumns billColumns = new BillColumns();
56
+ private PaymentColumns paymentColumns = new PaymentColumns();
57
+ }
58
+
59
+ @Getter
60
+ @Setter
61
+ public static class Tables {
62
+ private String billDetails = "bill_details";
63
+ private String paymentTransactions = "payment_transactions";
64
+ }
65
+
66
+ @Getter
67
+ @Setter
68
+ public static class BillColumns {
69
+ private String billId = "bill_id";
70
+ private String customerParamName = "customer_param_name";
71
+ private String customerParamType = "customer_param_type";
72
+ private String customerParamValue = "customer_param_value";
73
+ private String billAmount = "bill_amount";
74
+ private String billDate = "bill_date";
75
+ private String dueDate = "due_date";
76
+ private String billNumber = "bill_number";
77
+ private String billPeriod = "bill_period";
78
+ private String billStatus = "bill_status";
79
+ private String additionalInfo = "additional_info";
80
+ private String createdAt = "created_at";
81
+ private String updatedAt = "updated_at";
82
+ }
83
+
84
+ @Getter
85
+ @Setter
86
+ public static class PaymentColumns {
87
+ private String billId = "bill_id";
88
+ private String bbpsTxnRef = "bbps_txn_ref";
89
+ private String amountPaid = "amount_paid";
90
+ private String paymentMode = "payment_mode";
91
+ }
92
+ }