-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OD-18978 billing scripts restructure (#1260)
* OD-18978 feat: chargebee service * OD-18978 feat: job configuration for chargebee * OD-18978 fix: move logic from configs to job; remove some technical comments * OD-18978 feat: add replicated settings as cayenne entity; add billing.users settings to allowed for replication * OD-18978 feat: add upload of billing.users settings value to chargebee * OD-18978 feat: remove billing.users property from chargebee; add all required payment metrics * OD-18978 feat: local mode to test data without upload * OD-18978 refactor: move processing of properties to separate classes * OD-18978 fix: errors catching * OD-18978 fix: audit writing in local mode * OD-19292 feat: ignore of addons, that are not attached to subscriptions * OD-18978 feat: upgrade properties storage to preferences, add new properties * OD-18978 fix: description of office item * OD-18978 feat: new property for chargebee * OD-18978 fix: total office payments query * OD-18978 feat: replace long values for payment amounts with bigdecimal * OD-18978 fix: quantity representation * OD-18978 fix: office payments queries * OD-18978 fix: office query * OD-18978 fix: temp count number of payments instead of amount * OD-18978 fix: name of credit count property * OD-18978 fix: get double value instead of bigdecimal from db queries * OD-18978 fix: format of audit record * OD-18978 fix: convert of db query result --------- Co-authored-by: George Filipovich <[email protected]>
- Loading branch information
1 parent
a63f44f
commit 76f59d0
Showing
26 changed files
with
819 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
server/src/main/groovy/ish/oncourse/server/cayenne/Settings.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* | ||
* Copyright ish group pty ltd 2024. | ||
* | ||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. | ||
*/ | ||
|
||
package ish.oncourse.server.cayenne | ||
|
||
import ish.oncourse.server.cayenne.glue._Settings | ||
|
||
class Settings extends _Settings implements Queueable { | ||
@Override | ||
boolean isAsyncReplicationAllowed() { | ||
return false | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
server/src/main/groovy/ish/oncourse/server/services/chargebee/ChargebeeModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright ish group pty ltd 2024. | ||
* | ||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. | ||
*/ | ||
|
||
package ish.oncourse.server.services.chargebee; | ||
|
||
import com.google.inject.Provides; | ||
import com.google.inject.Singleton; | ||
import io.bootique.ConfigModule; | ||
import io.bootique.config.ConfigurationFactory; | ||
import ish.oncourse.server.ICayenneService; | ||
import ish.oncourse.server.PreferenceController; | ||
|
||
public class ChargebeeModule extends ConfigModule { | ||
|
||
@Singleton | ||
@Provides | ||
public ChargebeeService createChargebeeService(ConfigurationFactory configFactory, ICayenneService cayenneService, | ||
PreferenceController preferenceController) { | ||
return configFactory.config(ChargebeeService.class, getConfigPrefix()) | ||
.createChargebeeService(cayenneService, preferenceController); | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
server/src/main/groovy/ish/oncourse/server/services/chargebee/ChargebeeQueryUtils.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* Copyright ish group pty ltd 2024. | ||
* | ||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. | ||
*/ | ||
|
||
package ish.oncourse.server.services.chargebee | ||
|
||
class ChargebeeQueryUtils { | ||
|
||
public static final String TOTAL_CREDIT_PAYMENT_AMOUNT_QUERY_FORMAT = "SELECT SUM(p.amount) AS value" + | ||
" FROM %s p JOIN PaymentMethod pm on p.paymentMethodId = pm.id" + | ||
" WHERE pm.type = 2 " + | ||
" AND p.createdOn >= '%s'" + | ||
" AND p.createdOn < '%s'" + | ||
" AND p.status IN (3, 6)" | ||
|
||
public static final String TOTAL_CREDIT_PAYMENT_COUNT_QUERY_FORMAT = "SELECT COUNT(*) AS value" + | ||
" FROM %s p JOIN PaymentMethod pm on p.paymentMethodId = pm.id" + | ||
" WHERE pm.type = 2 " + | ||
" AND p.createdOn >= '%s'" + | ||
" AND p.createdOn < '%s'" + | ||
" AND p.status IN (3, 6)" | ||
} |
66 changes: 66 additions & 0 deletions
66
server/src/main/groovy/ish/oncourse/server/services/chargebee/ChargebeeService.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
* Copyright ish group pty ltd 2024. | ||
* | ||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. | ||
*/ | ||
|
||
package ish.oncourse.server.services.chargebee | ||
|
||
import com.google.inject.Inject | ||
import io.bootique.annotation.BQConfigProperty | ||
import ish.common.chargebee.ChargebeePropertyType | ||
import ish.oncourse.server.ICayenneService | ||
import ish.oncourse.server.PreferenceController | ||
import ish.oncourse.server.cayenne.Preference | ||
import org.apache.cayenne.query.ObjectSelect | ||
|
||
class ChargebeeService { | ||
private Boolean localMode = null | ||
|
||
|
||
@BQConfigProperty | ||
void setLocalMode(Boolean localMode) { | ||
this.localMode = localMode | ||
} | ||
|
||
Boolean getLocalMode() { | ||
return localMode | ||
} | ||
|
||
|
||
private ICayenneService cayenneService | ||
private PreferenceController preferenceController | ||
|
||
|
||
String getSubscriptionId(){ | ||
return preferenceController.getChargebeeSubscriptionId() | ||
} | ||
|
||
List<String> getAllowedAddons() { | ||
def addons = preferenceController.getChargebeeAllowedAddons() | ||
if(addons == null) | ||
return new ArrayList<String>() | ||
|
||
return addons.split(ChargebeePropertyType.ADDONS_SEPARATOR)?.toList() | ||
} | ||
|
||
String configOf(ChargebeePropertyType type) { | ||
def preference = ObjectSelect.query(Preference) | ||
.where(Preference.NAME.eq(type.getDbPropertyName())) | ||
.selectOne(cayenneService.newContext) | ||
|
||
if(preference == null) | ||
throw new IllegalStateException("Attempt to upload $type property to chargebee, but config was not replicated for this college") | ||
|
||
return preference.getValueString() | ||
} | ||
|
||
|
||
ChargebeeService createChargebeeService(ICayenneService cayenneService, PreferenceController preferenceController) { | ||
this.cayenneService = cayenneService | ||
this.preferenceController = preferenceController | ||
this | ||
} | ||
} |
167 changes: 167 additions & 0 deletions
167
server/src/main/groovy/ish/oncourse/server/services/chargebee/ChargebeeUploadJob.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
/* | ||
* Copyright ish group pty ltd 2024. | ||
* | ||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. | ||
*/ | ||
|
||
package ish.oncourse.server.services.chargebee | ||
|
||
import com.chargebee.Environment | ||
import com.chargebee.models.Subscription | ||
import com.chargebee.models.Usage | ||
import com.google.inject.Inject | ||
import ish.common.chargebee.ChargebeePropertyType | ||
import ish.oncourse.server.ICayenneService | ||
import ish.oncourse.server.PreferenceController | ||
import ish.oncourse.server.cayenne.Script | ||
import ish.oncourse.server.messaging.MessageService | ||
import ish.oncourse.server.scripting.api.EmailService | ||
import ish.oncourse.server.scripting.api.EmailSpec | ||
import ish.oncourse.server.scripting.api.MessageSpec | ||
import ish.oncourse.server.services.AuditService | ||
import ish.oncourse.server.services.chargebee.property.ChargebeePropertyProcessor | ||
import ish.oncourse.server.services.chargebee.property.ChargeebeeProcessorFactory | ||
import ish.oncourse.types.AuditAction | ||
import org.apache.cayenne.query.ObjectSelect | ||
import org.apache.logging.log4j.LogManager | ||
import org.apache.logging.log4j.Logger | ||
import org.quartz.DisallowConcurrentExecution | ||
import org.quartz.Job | ||
import org.quartz.JobExecutionContext | ||
import org.quartz.JobExecutionException | ||
|
||
import java.sql.Timestamp | ||
import java.time.Instant | ||
|
||
@DisallowConcurrentExecution | ||
class ChargebeeUploadJob implements Job { | ||
private static final Logger logger = LogManager.getLogger() | ||
|
||
@Inject | ||
private ICayenneService cayenneService | ||
|
||
@Inject | ||
private ChargebeeService chargebeeService | ||
|
||
@Inject | ||
private MessageService messageService | ||
|
||
@Inject | ||
private PreferenceController preferenceController | ||
|
||
@Inject | ||
private AuditService auditService | ||
|
||
private static Subscription subscription = null | ||
|
||
|
||
@Override | ||
void execute(JobExecutionContext context) throws JobExecutionException { | ||
logger.warn("ChargebeeUploadJob started") | ||
|
||
def addons = chargebeeService.getAllowedAddons() | ||
if(addons.isEmpty()) { | ||
logger.warn("ChargebeeUploadJob is rejected due to allowed addons not configured for this college") | ||
return | ||
} | ||
|
||
def site = chargebeeService.configOf(ChargebeePropertyType.SITE) | ||
def apiKey = chargebeeService.configOf(ChargebeePropertyType.API_KEY) | ||
|
||
|
||
if (site == null || apiKey == null) { | ||
String error = "Try to use chargebee, but its configs don't have necessary field (site or api key)" | ||
logger.error(error) | ||
throw new RuntimeException(error) | ||
} | ||
|
||
Calendar aCalendar = Calendar.getInstance() | ||
aCalendar.add(Calendar.MONTH, -1) | ||
aCalendar.set(Calendar.DATE, 1) | ||
def firstDateOfPreviousMonth = aCalendar.getTime() | ||
|
||
aCalendar.add(Calendar.MONTH, 1) | ||
aCalendar.set(Calendar.DATE, 1) | ||
def firstDateOfCurrentMonth = aCalendar.getTime() | ||
|
||
def propertiesToUpload = ChargebeePropertyType.getItems() | ||
.findAll {addons.contains(it.getDbPropertyName())} | ||
|
||
logger.warn("Chargebee start date including $firstDateOfPreviousMonth , end date $firstDateOfCurrentMonth") | ||
|
||
try { | ||
if(!chargebeeService.localMode) | ||
Environment.configure(site, apiKey) | ||
|
||
propertiesToUpload.each { type -> | ||
def property = ChargeebeeProcessorFactory.valueOf(type, firstDateOfPreviousMonth, firstDateOfCurrentMonth) | ||
uploadUsageToSite(property) | ||
} | ||
} catch (Exception e) { | ||
logger.catching(e) | ||
throw e | ||
} | ||
|
||
logger.warn("ChargeebeeUploadJob executed successfully") | ||
} | ||
|
||
|
||
private void uploadUsageToSite(ChargebeePropertyProcessor propertyProcessor) { | ||
if(propertyProcessor.type == null) { | ||
throw new IllegalArgumentException("Try to upload chargebee usage without item type") | ||
} | ||
|
||
String itemPriceId = chargebeeService.configOf(propertyProcessor.type) | ||
if(itemPriceId == null) | ||
throw new IllegalArgumentException("Try to upload usage $propertyProcessor.type without configured item id") | ||
|
||
def quantity = propertyProcessor.getValue(cayenneService.dataSource) | ||
logger.warn("Try to upload to chargebee $propertyProcessor.type with id $itemPriceId value $quantity") | ||
|
||
if(Boolean.TRUE == chargebeeService.localMode) | ||
auditService.submit(ObjectSelect.query(Script).selectFirst(cayenneService.newReadonlyContext), AuditAction.SCRIPT_EXECUTED, "Try to upload to chargebee $propertyProcessor.type with id $itemPriceId value " + quantity.toPlainString()) | ||
else { | ||
def subscription = getSubscription() | ||
if(!subscription.subscriptionItems().find {it.itemPriceId() == itemPriceId}) { | ||
logger.warn("Item price id $itemPriceId not allowed for subscription $chargebeeService.subscriptionId and will be ignored") | ||
return | ||
} | ||
uploadToChargebee(itemPriceId, quantity.toPlainString()) | ||
} | ||
} | ||
|
||
private void uploadToChargebee(String itemPriceId, String quantity) { | ||
try { | ||
Usage.create(chargebeeService.subscriptionId) | ||
.itemPriceId(itemPriceId) | ||
.quantity(quantity) | ||
.usageDate(new Timestamp(Instant.now().toEpochMilli())) | ||
.request() | ||
} catch (Exception e) { | ||
logger.error("Chargebee usage upload error: " + e.getMessage()) | ||
messageService.sendMessage(new MessageSpec().with { | ||
it.subject = 'onCourse->Chargebee usage upload error. Contact ish support' | ||
it.content ="\n$itemPriceId upload error for college $preferenceController.collegeName. Reason: $e.message" | ||
it.from(preferenceController.emailFromAddress) | ||
it.to("[email protected]") | ||
it | ||
}) | ||
} | ||
} | ||
|
||
private Subscription getSubscription(){ | ||
if(subscription != null) | ||
return subscription | ||
|
||
|
||
def subscriptions = Subscription.list().id().is(chargebeeService.subscriptionId).request() | ||
if(subscriptions.empty) { | ||
throw new IllegalArgumentException("Subscription with id $chargebeeService.subscriptionId not found!") | ||
} | ||
|
||
subscription = subscriptions.first().subscription() | ||
return subscription | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
.../groovy/ish/oncourse/server/services/chargebee/property/ChargebeePropertyProcessor.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
* Copyright ish group pty ltd 2024. | ||
* | ||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. | ||
*/ | ||
|
||
package ish.oncourse.server.services.chargebee.property | ||
|
||
import ish.common.chargebee.ChargebeePropertyType | ||
|
||
import javax.sql.DataSource | ||
import java.text.SimpleDateFormat | ||
|
||
abstract class ChargebeePropertyProcessor { | ||
private static final SimpleDateFormat SQL_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") | ||
|
||
private Date startDate | ||
private Date endDate | ||
|
||
ChargebeePropertyProcessor(Date startDate, Date endDate) { | ||
this.startDate = startDate | ||
this.endDate = endDate | ||
} | ||
|
||
protected String getFormattedStartDate(){ | ||
return SQL_DATE_FORMAT.format(startDate) | ||
} | ||
|
||
protected String getFormattedEndDate() { | ||
return SQL_DATE_FORMAT.format(endDate) | ||
} | ||
|
||
abstract BigDecimal getValue(DataSource dataSource) | ||
abstract ChargebeePropertyType getType() | ||
} |
27 changes: 27 additions & 0 deletions
27
...y/ish/oncourse/server/services/chargebee/property/ChargebeeSimplePropertyProcessor.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright ish group pty ltd 2024. | ||
* | ||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. | ||
*/ | ||
|
||
package ish.oncourse.server.services.chargebee.property | ||
|
||
import ish.oncourse.server.util.DbConnectionUtils | ||
|
||
import javax.sql.DataSource | ||
|
||
abstract class ChargebeeSimplePropertyProcessor extends ChargebeePropertyProcessor { | ||
|
||
ChargebeeSimplePropertyProcessor(Date startDate, Date endDate) { | ||
super(startDate, endDate) | ||
} | ||
|
||
@Override | ||
BigDecimal getValue(DataSource dataSource) { | ||
return DbConnectionUtils.getBigDecimalForDbQuery(getQuery(), dataSource) | ||
} | ||
|
||
abstract String getQuery() | ||
} |
Oops, something went wrong.