verida / blockchain-contracts

Smart contracts, part of the protocol powering the Verida Network
https://www.verida.network
Apache License 2.0
7 stars 2 forks source link

Implement service registry contract #24

Open tahpot opened 2 years ago

tahpot commented 2 years ago

This task is for the smart contract implementation of Service Registry - Phase 1.

Smart contract requirements

The following smart contract requirements have been scoped based on the user stories in Phase 1.

Architecture overview

Service Registry - Smart Contract Overview.png

Methods:

registerService(did: string, serviceType: ServiceType, endpointUri: string, country: string, maxAccounts: integer, pricePerDayPerAccount: decimal, proof: string, consentBlock: integer)

Enable a Verida Infrastructure Operator to register a service.

proof is a signed message in the form:

${did} consents to registering a ${serviceType} at ${endpointUri}

Constraints:

updateService(did: string, serviceId: string, maxAccounts: integer, pricePerAccount: decimal)`

Update details about the service. Note: Changing the country or endpoint URI is not permitted. Instead a new service type should be created and the current one deregistered.

Updating maxAccounts will take effect immediately.

Updating pricePerAccount will take effect after 30 days (priceChangeDelayDays) for existing accounts (to give sufficient notice of any changes) if the price increases. The price change will take effect immediately for all new accounts.

Constraints:

deregisterService(did: string, serviceId: string)`

Enable a Verida Infrastructure Operator to deregister a service. There will be a 30 day delay (see deregisterDelayDays) before the service is removed entirely. The service will immediately stop accepting new connections. The service will be flagged as pending removal.

addCredit(did: string)

Allow a Verida Account to deposit VDA tokens as credit to their account. Credit is stored against a given DID. This can be used by both Verida Account users and Verida Infrastructure Operator users.

removeCredit(did: string)

Allow a Verida Account to withdraw VDA tokens that are currently sitting as credit in their account. Tokens will be withdrawn to the account that called this method.

The smart contract will reject the request if, after removing the tokens, the did will have insufficient credit to meet 30 days (minimumDaysCreditPerService) for all connected services.

connectService(did: string, serviceId: string)

Enable a Verida Account to connect and make use of a service provided by a Verida Infrastructure Operator. The Verida Account must deposit sufficient tokens for 30 days (minimumDaysCreditPerService) of utilising the service, calculated based on the pricePerDayPerAccount.

The smart contract will reject the connection if:

disconnectService(did: string, serviceId: string)

Enable a Verida Account to disconnect a service provided by a Verida Infrastructure Operator. The Verida Account will free up tokens previously deposited for utilising the service that can then be removed via removeCredit().

discoverServices(infraType: string (required), serviceType: string, country: string, maxPricePerDay)

Enable easy searching of the available services so users can discover the services they want to select.

distribute()

Distribute credit between all the registered did's based on the connected services.

For each Verida Account Connection credit VDA tokens to the Verida Infrastructure Operator and debit tokens from the Verida Account base on the service pricePerDayPerAccount.

If the Verida Account has insufficient credit the services they have registered are automatically de-registered. There is no guarantee to the order that services are de-registered.

Credit can be added or removed (effectively claiming) via the addCredit() and removeCredit() methods.

Does this method need to be called externally or could it be automatically called every day?

Constraints:

DID v blockchain address

Most smart contracts allocate tokens to a blockchain address. In our instance, we need to allocate tokens to a specific DID and only allow those tokens to be withdrawn by that DID.

Data Structures:

InfrastructureType (enum):

[
  "database",
  "storage",
  "messaging",
  "notification"
]

ServiceType (lookup):

[
  "VeridaDatabase": {"type": "database"},
  "VeridaStorage": {"type": "storage"},
  "VeridaMessaging": {"type": "messaging"},
  "VeridaNotification": {"type": "notification"}"
  "TextileDatabase": {"type": "database"},
]

ServiceTypeConfig:

The configuration will depend on the ServiceType.

Config Variables:

These variables can be changed by the contract owner.

Deliverables

Future considerations

tahpot commented 2 years ago

I need to scope this during the sprint.

tahpot commented 2 years ago

First draft of scope complete.

Handover call with @BlockchainCrazy95 complete.

BlockchainCrazy95 commented 2 years ago
  1. registerService(... pricePerDayPerAccount: decimal, ...)

    • How can I determine the decimal of pricePerDayPerAccount? ex: if decimal is 9 and the pricePerDayPerAccount is 0.1 VDA, I have to call the function with value 100,000,000.
    • I think, block number will be stored in the function using block.timestamp. Need to reduce the consentBlock parameter.
    • Decimal problem is same in updateService function.
  2. Need to check the usage of did(:string) value.

  3. How can I realize if he is a Verida Infrastructure Operator or Verida Account?

tahpot commented 2 years ago
  1. How can I determine the decimal of pricePerDayPerAccount?

pricePerDayPerAccount will be in VDA tokens. The decimal points should match our ERC-20 token.

  1. Need to check the usage of did(:string) value

A did is a decentralized identifier. It's a unique reference for an account on the Verida network. You can think of it like a blockchain address, except it's an address on the Verida network.

  1. How can I realize if he is a Verida Infrastructure Operator or Verida Account?

I don't think you need to identify them.

I've given them separate names so the user stories make more sense.

BlockchainCrazy95 commented 2 years ago

@tahpot, when can config variables be changed?

BlockchainCrazy95 commented 2 years ago
BlockchainCrazy95 commented 2 years ago

Here's some questions for Service Registry contract.

Finally, I changed the estimation time from Monday to Wednesday. Is it okay?

tahpot commented 2 years ago
  • Can ServiceType be deleted later?

No.

  • In registerService() function, I think, we need to keep both msg.sender as service register and did. Because another account(not the creator) can update service if he knows the did. So we need to keep and check the owner of service.

No, we don't want to link the msg.sender and the did. The purpose of the proof attribute is to provide a signature that can be verified it was signed by the did. Don't worry about implementing that signature check yet, we can do that at the end. I have another project that has code to make this easy.

  • When the Verida Infrastructure Operator or Verida Account deposit VDA tokens, Verida get fee from them? Or just staked total amount?

No fee.

  • When the Verida Infrastructure Operator register service, proof is made from client side?

Yes.

  • Verida Infrastructure Operator can also be Verida account?

Verida Infrastructure Operator is a type of Verida account being used by an infrastructure provider.

  • When Verida Account add credit, there need to be vdaTokenAmount as a function parameter.

Ok.

  • What is the purpose of distribute() function?

This function is designed to pay the infrastructure operators and take a fee from the Verida account holder who is using the infrastructure.

It's an automated way to pay for accessing server infrastructure.

If this function is needed, the function must be called externally.

Ok.

Finally, I changed the estimation time from Monday to Wednesday. Is it okay?

Okay.

BlockchainCrazy95 commented 2 years ago

updateService(did: string, serviceId: string, maxAccounts: integer, pricePerAccount: decimal)

1updateService

I have some stuffs to discuss relating to the service update. Above image shows that the payment methods when we update the pricePerAccount. There's no problem in first update (day 3). The problem occurs in the second update (day 10) - after 7 days from first update. As first update delay duration (day 3 ~ day 33) hasn't finished yet, we should decide the second update duration and price. In this case, we can use above two methods (method1 or method2) for accounts' payment. Each method has their own pros and cons. Method1: It's more precise than method 2, but managing state variables might be complicated. Furthermore, if we change the priceChangeDelayDays here, managing state variables might be more and more complicated. Method2: Managing state variables is easier than method 1.

I need to confirm these problems to finish Service Registry contract completely.

BlockchainCrazy95 commented 2 years ago

distribute()

We can't iterate all the registered dids on the connected services. It can cause some problems, including gas fee.

For each Verida Account Connection credit VDA tokens to the Verida Infrastructure Operator and debit tokens from the Verida Account base on the service pricePerDayPerAccount.

We can implement this feature as defining claim function. Verida Infrastructure Operator will get VDA tokens using claim function when we need.

If the Verida Account has insufficient credit the services they have registered are automatically de-registered. There is no guarantee to the order that services are de-registered.

We can make view function which check the account availability on service. We can call this view function externally when we need (every day or whenever we need)

function getAvailability(address identity, bytes32 serviceId, bytes signature) public view { ... };
tahpot commented 2 years ago

There's no problem in first update (day 3). The problem occurs in the second update (day 10) - after 7 days from first update.

I suggest we only permit a Verida Infrastructure Operator changing their price once per 30 days. This avoids them changing the price until a previous price change has completed.

We can implement this feature as defining claim function. Verida Infrastructure Operator will get VDA tokens using claim function when we need.

Ideally we can distribute at the end of the day. removeCredit() is effectively a claim function to pull out any unused tokens owned by a DID.

We can make view function which check the account availability on service. We can call this view function externally when we need (every day or whenever we need)

I agree. Let's allow a service availability check. Perhaps we call it checkServiceAvailability()?

However, I think we are best to record the state of a service in the smart contract. ie: service.status = "active|disabled|pendingPayment".

tahpot commented 2 years ago

If we change priceChangeDelayDays, how can we apply the payment method?

priceChangeDelayDays can be changed by the contract owner.

When a price change occurs, the date the price change takes effect is calculated based on the current value of priceChangeDelayDays.

No further price changes can occur until that date has elapsed.

priceChangeDelayDays may change, however the new value won't take effect until the previous price change has been completed.

tahpot commented 2 years ago

proof: string

This will be a common requirement across all our smart contracts.

Don't worry about implementing this yet. We can implement at the end.

BlockchainCrazy95 commented 2 years ago

In distribute() function, we have to iterate all the DIDs and serviceIds to check their status. It will consume much gas fees. But as I suggested yesterday, if we use claim function, we can reduce the gas fees. Actually, Verida Infrastructure Operator DID will be sent to claim function as a parameter. So claim function will iterate only services that specific DID is connected to. And we can ask Verida Infrastructure Operators to claim every day.

BlockchainCrazy95 commented 2 years ago

I suggest that accounts have to bond VDA tokens to the contract when they connect to the service, not at every interval.

Verida Accounts should bond when they connect to the service(for 30 days), but they only lose their tokens when the infrastructure operator claims tokens.

Verida Infrastructure Operator and Verida Account have to bond tokens before they're going to register or connect to the service.

tahpot commented 2 years ago

Verida Infrastructure Operator can claim tokens once per week (at a fixed day/time ie: Thursday 9am UTC). Verida Infrastructure Operator must claim tokens within 14 days or they lose the right to those tokens.

BlockchainCrazy95 commented 2 years ago

I mean, we don't need to check this constraint in removeCredit() function. Because all the tokens will be bonded to the contract when accounts are going to connect service.

BlockchainCrazy95 commented 2 years ago

Basically completed the Service Registry smart contract and unit test.

Remaining:

I think, mainly tested all about its features, but please have a look and give me your feedback.

BlockchainCrazy95 commented 2 years ago

@tahpot, can you give me some examples of Service Registry updating process? I want examples with detailed numbers in case of above comments.

tahpot commented 2 years ago

@BlockchainCrazy95

I have added a new method to implement (discoverServices()).

@tahpot, can you give me some examples of Service Registry updating process?

Let's discuss this on our call. I'm not sure exactly what you mean.

BlockchainCrazy95 commented 2 years ago

Diamond Standard

A diamond is a contract with external functions that are supplied by contracts called facets. Facets are separate, independent contracts that can share internal functions, libraries and state variables.

Diamond standard contracts will include facets. Facets can be defined by external functions or internal functions and state variables. Diamond standard contracts will include as follows: DiamondCut, DiamondLoupe, DiamondStorage, Facet contracts...

In this service registry contract, facets can be ServiceMngFacet which Verida Infrastructure Operator can operate to and ConnectionFacet which Verida Account will do something with the registered services.

pranavburnwal commented 2 years ago

cc @ITStar10

ITStar10 commented 2 years ago

image First, once user has not enough credits, is he disconnected from all connected services automatically? So, he needs to connet services again later after he add credit?

Otherwise, if user isn't disconnected but not able to use service till he add credit, does he deposit his credit to the service as done in connecting service? When a user connect a service, he should pay the amount of "price * minimumDays". Do we need 'chargeCredit' function that deposit user's credit into service? If so, how much need to be deposited? Same amount as 'connectService'? In this case, is user responsible to pay credits for non-used days?

Second, we need to separate updateService() function for maxAccount & pricePerDayPerAccount. Because, operators can update maxAccount anytime. But there is delay for pricePerDayPerAccount.

Third, we need to update checkServiceAvailability() function. This function is not implemented correctly. And it will depends on the answer of first question above.

ITStar10 commented 2 years ago

Service priceChangeDelayDays problem

image Let's say an operator updated the price. And here priceChangeDelayDays is 30. So updated price will be applied after 30 days. After 10days, owner of our contract updated "priceChangeDelayDays" into 20.

Question

When will be the updated price applied? After 10days from when owner updated "priceChangeDelayDays", because that days is after 20 days(t3) from when operator updated the price? Or will the updated price be applied after 30 days (t4) from when operator updated?

Suggestion

I think second one shoud be better. Because service operators updates their price considering the priceChangeDelayDays on their side.

ITStar10 commented 2 years ago

Service price update problem

Now, the service operator can only update price once per each duration of "priceChangeDelayDays".

ITStar10 commented 2 years ago

vdaPerAccount problem

Owner (we - VeridaCompany) can update the price of vdaPerAccount. Let's say original vdaPerAccount of a service was 30 at registering time. And operator registered a service with value of 100 for 'maxAccount'. So operator deposited 30*100 credits at register. After operator registered a service, owner changed the vdaPerAccount for this service type into 60.

1. Problem on decreasing 'maxAccount'

Here, if the operator is trying to decrease 'maxAccounts' into 50, how much do we send back credits to operator? *Amount by original price : 30 (100 - 50)?** or *Amount by new price: 60 (100 - 50) ?** Of course, the first one, right?

2. Problem on increasing 'maxAccount'

Let's say operator is going to increase 'maxAccount' into 200. How much does opeartor deposit credit more? *By original price: 30 (200 - 100) ?** or *By new price: 60 (200 - 100)?**

3. Problems will be occurred when we use updated price for calculating credit amount

Time Register vdaPerAccount Update - 1 maxAccount Update - 1 vdaPerAccount Update - 2 maxAccount Update - 2 vdaPerAccount Update - 3 maxAccount Update - 3
Done by Operator Owner Operator Owner Operator Owner Operator
vdaPerAccount 10 30 20 15
maxAccount 40 70 50 100
Necessary credits 400 ? ? ? ? ? ?

Do we need to check out all the services on each update of 'vdaPerToken'? When the owner updates the vdaPerAccount from 10 to 30, we can't request all the existing services to update credits. Let's say an operator registered his service with value 10 for 'vdaPerToken'. Once vdaPerToken become 30, do we need to request to the operator deposit more credits?

Solution

Here, I suggest 2 solutions:

  1. Service operators use vdaPerAccount of registering time on updates of 'maxAccount'. Pros : Make code simple when vdaPerAccount changes continuously. Cons : If the vdaPerAccount decrease later, it won't be applied to the service.
  2. Service operators use current vdaPerAccount on updates of 'maxAccount'. Pros : This can be acceptable for service operators. Cons : We(-Verida company) can't request service operators to deposit credits more when we increase vdaPerAccount.

Which solution will be better? (Now implementing in 2nd method)?

tahpot commented 2 years ago

@ITStar10

First, once user has not enough credits, is he disconnected from all connected services automatically? So, he needs to connet services again later after he add credit?

I don't think we want to disconnect the services as the user will have to manually go and reconnect each service. Rather, perhaps we have a status on Verida Account Connection that is unpaid?

Otherwise, if user isn't disconnected but not able to use service till he add credit, does he deposit his credit to the service as done in connecting service?

He just calls addCredit(). That method will need to then update any Verida Account Connection that is unpaid to be active.

When a user connect a service, he should pay the amount of "price * minimumDays".

He doesn't pay at the time of connection, rather the tokens are locked. The number of locked tokens is servciePrice * minimumDaysCreditPerService *.

Do we need 'chargeCredit' function that deposit user's credit into service? If so, how much need to be deposited? Same amount as 'connectService'? In this case, is user responsible to pay credits for non-used days?

That was the original purpose of the distribute() method in the original scope (see top). The amount to charge is pricePerDayPerAccount specified when registering the service.

Second, we need to separate updateService() function for maxAccount & pricePerDayPerAccount. Because, operators can update maxAccount anytime. But there is delay for pricePerDayPerAccount.

Can maxAccounts or pricePerAccount be null parameters in updateService() and then ignored?

Third, we need to update checkServiceAvailability() function. This function is not implemented correctly. And it will depends on the answer of first question above.

Ok.


Service priceChangeDelayDays problem

When will be the updated price applied?

  1. After 10days from when owner updated "priceChangeDelayDays", because that days is after 20 days(t3) from when operator updated the price?
  2. Or will the updated price be applied after 30 days (t4) from when operator updated?

I agree with option (2).


Service price update problem

Now, the service operator can only update price once per each duration of priceChangeDelayDays.

If the service operator would like to cancel current update & set new price update operation, can we do this?

No. Not required.


vdaPerAccount problem

  1. Problem on decreasing 'maxAccount' Here, if the operator is trying to decrease 'maxAccounts' into 50, how much do we send back credits to operator? Amount by original price : 30 * (100 - 50)?

Yes, Amount by original price : 30 * (100 - 50)

  1. Problem on increasing 'maxAccount' Let's say operator is going to increase 'maxAccount' into 200. How much does opeartor deposit credit more?

In this scenario the operator needs to deposit:

So the formula is:

(oldPrice - newPrice) * (oldMaxAccount) + newPrice * (oldMaxAccount - newMaxAccount)

  1. Problems will be occurred when we use updated price for calculating credit amount

When the owner updates the vdaPerAccount from 10 to 30, we can't request all the existing services to update credits. Let's say an operator registered his service with value 10 for 'vdaPerToken'. Once vdaPerToken become 30, do we need to request to the operator deposit more credits?

Here, I suggest 2 solutions:

  1. Service operators use current vdaPerAccount on updates of 'maxAccount'.

Agree, I think (2) is best. However, can we ensure that they can't call registerService(), updateService() or deregisterService() until they have added enough credit to allow for the updated vdaPerAccount value?

ITStar10 commented 2 years ago

Can maxAccounts or pricePerAccount be null parameters in updateService() and then ignored?

There is no null value for uint in Solidity. So these input values can be set 0 instead of 'null' on function calls.

So, I suggest to separate updateService() function for 'maxAccount' & 'pricePerAccount'.

ITStar10 commented 2 years ago

Agree, I think (2) is best. However, can we ensure that they can't call registerService(), updateService() or deregisterService() until they have added enough credit to allow for the updated vdaPerAccount value?

Let's see following case: Operator register a service with 10 for vdaPerAccount & 20 for maxAccounts. After a while, vdaPerAccount is updated to 15. Service operator needs to deposit 5*20 credits more to this service.

We need addCreditsForService(serviceId, ...) or addCreditsForInsufficientServices(...) for service operator.

Current addCredit() function only deposits credits to our contract, but not on service. When operator register service, 200 credits are removed from operator's credit amount and are locked in service. So we need some functions to deposit more credits on individual service. which function do we need?

Suggestion

I think we need both functions. A service operator can run several services of same infuraType. Once vdaPerAccount updated, they should deposit credits on every services of same infuraType. In case, they will prefer to resolve all services by one function call.

Is service available when service operator doesn't deposit enough credits?

In the above example, service had been available till before vdaPerAccount updated. After vdaPerAccount updated, service operator didn't deposit enough credits (5*20). Is this service available for users?

We can set up service in on-hold status till before operator deposit enough credits.

Service operators use our contract's function named 'checkServiceAvailability(serviceId, userDID, ...)' to allow their services for only connected users. They should call this functions absolutely, if not any unconnected users can use their services. So if service operator didn't pay enough credits on this service, we can return false inside 'checkServiceAvailability' function for any users including connected users.

Of course, this might be a good choice for us (Verida-Company), but please consider whether 'holding-up service' can be acceptable for service operators. Is this feature ncecessary on our contract?

tahpot commented 2 years ago

@ITStar10 Noted.

I'd like flexibility to easily add new variables in the future. As such, can we do this?

updateService(did: string, serviceId: string, key: string, value: any)

Thoughts @pranavburnwal ?

ITStar10 commented 2 years ago

Restrict operations while being services with insufficient credits

Service operator runs 2 services of service#1 & service#2. After vdaPerAccount updated by contract owner(us - verida), credits deposited before is not enough for service#2.

Question

What will be restricted to this service operator?

1. Service operator will be restricted for all operations:

ITStar10 commented 2 years ago

Rules for Service Operator & Users

Service Operator Rule

1. Operator should add credits before registering services

2. Operator should lock following amount of credits in the service at registering:

Locked Amount = vdaPerAccount * maxAccountsOfService

Operator'd be restricted if he didn't add credit:

Though operator is restricted, service will run continuously. Because, users had already deposited their credits to use this service. If we restrict running service, it will make code complicated to calculate user fees. Becasue there are several conditions to consier while calculating service fees.

Operator will not be restricted on his other services:

User Rule

1. Before connecting to a service, user should add credits.

2. A user connects to a service to use. User should lock following amount of credits into connecting service:

Locked Amount = servicePrice * minimumDaysCreditPerService

Once connected, user can use this service for 'minimumDaysCreditPerService' days. This will not be affcted once 'servicePrice' or 'minimumDaysCreditPerService' updated.

If user disconnect this service before 'minimumDaysCreditPerService' passed, credits for remaining days will send back to the user.

3. User should pay fees for service usage.

At first 'minimumDaysCreditPerService' days, the price will be calculated by 'servicePrice' of registered time. After that, user should pay fees by updating 'servicePrice'.

Fees are calculated when the service operator requests to claim. Users should pay the fees by 'servicePrice' of claiming time. Fees will be automatically send to the service on claiming request by service operator. To pay the fees, user should has credits in contract.

If user doesn't have enough credits to pay the fees, user can not use service until before he adds enough credits. User should pay for unpaid days though he couldn't use the service.

Ex: User connects to a service with 5 days for 'minimumDaysCreditPerService' and 3 for 'servicePrice'. He has 22 credits at connecting time. After connected, user has 7 credits(= 22 - 3 5) in the contract. User can use the service for first 5 days. As he has 7 credits more, he can use 2 days more after first 5 days. After 7 days, his remaining credit is only 1( = 7 - 3 2). And he can't use the service. User doesn't add credits and pass 4 days(11 days from start). After 11 days from the start, user adds 30 credits. Here, our contract calculate for fees that user unpaid. Among added 30 credits, 12(= 3 * 4) credits are for unpaid days. And remaining credits will be 18( = 30 - 12). So he can use service 6(= 18 / 3) days more after he added 30 credits.

ITStar10 commented 2 years ago

Need withdraw() function

If an operator didn't claim in 14 days, this claim amount will be owned to contract. So we need a function to withdraw these credits.

Unpaid users on removeService()

When service operator calls removeService(), there might be users who didn't pay fees. Should ignore these?

pranavburnwal commented 2 years ago

@ITStar10 Noted.

I'd like flexibility to easily add new variables in the future. As such, can we do this?

updateService(did: string, serviceId: string, key: string, value: any)

Thoughts @pranavburnwal ?

I don't think we should do those now. We can leverage upgradable contracts for this.

Maybe even consider the diamond storage

ITStar10 commented 2 years ago

User disconnect problem

image

Condition

Here, service price is 5 and no updates till t3. minimumDaysCreditsPerService is 10 and no updates till t3. User has 50 credits at t1.

Story

User connects to a service at t1 and use this service for 10( = 50 / 5) days. After 10 days (at t2), user doesn't have any credits. He doesn't add credits till t3. At t3 point, if user wanted to use this service, he should pay for unused days - 20 days from t2 to t3. And he should pay more to further use. So, if he wanted to use 10 more days from t3, he should add 150 credits (100 for past 20 days, 50 for current use) totally.

Problem

If the user is allowed to disconnect service at t3, he can disconnect this service. And connect service again. Then he should add only 50 credits for minimumDaysCredits and can use 10 days.

Question

Can user disconnect this service at t3?

Check service availability issue for a user

Story

User connected 2 services of service#1 & service#2. He passed 'minimumDaysCreditPerService' for both services, so he should pay by credits to use services. He has 10 credits. And the price of services are 6 and 8 respectively.

Problem

User can't use 2 services together at this point, he needs 14(6+8) credits to be allowed for both services. We(-Verida) should allow only one service.

Question

Among thse 2 services, which one can we allow to this user?

pranavburnwal commented 2 years ago

Question

Can user disconnect this service at t3?

I think we something like 3 grace days (like in fintech) for users to cancel. So like if a user is out of credits at T2, He can cancel for free (pay no extra credit till T2 + 3 days.

So following the story the user finishes credits in T2, if they would have canceled by T2+3days they would not be charged. Instead, they have canceled by T3, so they will have to pay by 17 days extra (they use it or not) if the service is active they should, 3 days grace.

PS: Just my thoughts, needs discussion.

Question

Among thse 2 services, which one can we allow to this user?

My thought will be to disable both. Less than needed balance so just block both. We don't have 'priority' and/or 'weightage' between services so we cannot choose. Ideally, the other option will be to just allow the first service that gets accessed first.

@tahpot Thoughts?