Satellite billing system combines stripe and coinpayments API for credit card and cryptocurrency processing. It uses `satellite/accounting` pkg for project accounting. Billing is set on account but that is the subject for future changes as we want billing to be on a project level. That requires decoupling stripe dependency to the level where we utilize only credit card processing and maintain all other stuff such as customer balances and invoicing internally. Every satellite should have separate stripe and coinpayments account to prevent collision of customer related data such as uuid and email.
Stripe operates on a basis of customers. Where customer is, from stripe doc: Customer objects allow you to perform recurring charges, and to track multiple charges, that are associated with the same customer. The API allows you to create, delete, and update your customers. You can retrieve individual customers as well as a list of all your customers. Satellite billing doesn't uses `customer` concern with public API, so it is treated as implementation detail. Stripe customer balance is automatically applied to invoice total before charging a credit card.
Stripe billing system implementation stores a customer reference for every user:
```
model stripe_customer (
key user_id
unique customer_id
field user_id blob
field customer_id text
field created_at timestamp ( autoinsert )
)
```
# Public interface
Satellite payments exposes public API for console to use. It includes account related behavior that customers can use to get his/her billing information and to interact with billing system, adding credit cards and making token deposits. The top level interface `payments.Accounts` exposes account level interaction with the system. Having an interface allows to disable billing by using `satellite/payments/mockpayments` implementation which basically is just a stub that does nothing. All methods requires satellite user id, then service maps it to related stripe customer id.
```go
// Accounts exposes all needed functionality to manage payment accounts.
//
// architecture: Service
type Accounts interface {
// Setup creates a payment account for the user.
// If account is already set up it will return nil.
Every satellite user has a corresponding customer entity on stripe which holds credit cards, balance which reflects the amount of STORJ tokens, and is used for invoicing. Every time a user visits billing page on the satellite UI we try to create a customer for him if one doesn't exists.
Credit cards processing is done via stripe. Every stripe customer can be attached a credit card, which can be used to pay for the customer invoice. Upon adding first card is automatically marked as default. If a customer has more than one card, any of the cards can be made default. Default credit card is automatically applied to new invoice as default payment method if corresponding setting is not set explicitly during invoice creation. No data is stored for the credit cards in satellite db. All methods are just wrappers around stripe API.
```go
// CreditCards exposes all needed functionality to manage account credit cards.
//
// architecture: Service
type CreditCards interface {
// List returns a list of credit cards for a given payment account.
Clearing chore runs cycles which reconcile transfers and balance. It consists of transaction update cycle that updates pending transactions states and account update cycle that updates stripe customer account balance, applying successfully completed transactions.
```go
// Chore runs clearing process of reconciling transactions deposits,
STORJ cryptocurrency processing is done via coinpayments API. Every time a user wants to deposit some amount of STORJ token to his account balacne, new coinpayments transaction is created. Transaction amount is set in USD, and conversion rates is being locked(saved) after transaction is created and stored in the db.
There is a cycle that iterates over all `pending`(`pending` and `paid` statuses of coinpayments transaction respectively) transactions, list it's infos and updates tx status and received amount. If updated is status is set to `cancelled` or `completed`, that transactions won't take part in the next update cycle. When there is a status transaction to `completed` along with the update `apply_balance_transaction_intent` is created. Transaction with status `completed` and present `apply_balance_transaction_intent` with state `unapplied` defines as `UnappliedTransaction` which is later processed in update balance cycle. If the received amount is greater that 50$ a promotional coupon for 55$ is created.
Cycle that iterates over all `unapplied` transactions, adjusting stripe customer balance for transaction received amount. Transaction is consumed prior updating customer balance on stripe to prevent double accounting. The idea is that we rather not credit customer than credit twice. Created customer balance transaction holds id of applied transaction in it's meta, therefore there is room for verification loop which iterates over customer balance transactions, reseting state for transaction which id hasn't been found.
```go
// applyTransactionBalance applies transaction received amount to stripe customer balance.
Invoice include project usage cost as well as any discounts applied. Coupons and credits applied as separate invoice line items, therefore it reduce total due amount. Next applied STORJ token amount which is repesented as credits on custmer balance if any. If invoice total amount is greater than zero after bonuses and STORJ tokens, default credit card at the moment of invoice creation will be charged. If total amount is less than 1$, then stripe won't try to charge credit card but increase debt on customer balance.
Invoice creation consist of few steps. First invoice project records have to be created. Each record consist of project id, usage and timestamps of the start and end of billing period. This way we ensure that usage is the same during all invoice creation steps and there won't be two or more invoices created for the same period(actually only invoice line items for certain billing period and project are ensured not to be created more than once). Coupon usages are also created during this step, which are later used to create coupon invoice line items.
Create project records for all projects for specified billing period. Billing period defined as `[0th nanosecond of the first day of the month; 0th nanosecond of the first day of the following month)`.
Project record contains project usage for some billing period. Therefore, it is impossible to create project record for the same project and billing period.
```go
// ProjectRecord holds project usage particular for billing period.
type ProjectRecord struct {
ID uuid.UUID
ProjectID uuid.UUID
Storage float64
Egress int64
Objects float64
PeriodStart time.Time
PeriodEnd time.Time
}
```
This command sets billing period and iterates over all projects on the satellite creating invoice project records.
```go
// PrepareInvoiceProjectRecords iterates through all projects and creates invoice records if
// none exists.
func (service *Service) PrepareInvoiceProjectRecords(ctx context.Context, period time.Time) (err error) {
Next step is to create invoice line items for project usage from invoice project records. We iterate through all unapplied invoice project records creating invoice line item for each of them. Stripe invoice line items are connected to stripe customer, each project line item that belongs to the same customer will be gathered under one invoice and billed at once. Before creating line item, project record is consumed so it won't participate in the next loop. If any error during creation of the invoice line item on stripe, invoice project record state can be reset manually to create line item for the project in the next loop. It also possible to include this item in the invoice for next billing period.
To create invoice project line items:
```bash
inspector payments create-invoice-items
```
Iterate over all project records, calculating price and creating invoice line item for each. Project record that has project owner without corresponding stripe customer is skipped, therefore, this record will participate in the next loop, until the record is consumed or deleted.
```go
// applyProjectRecords applies invoice intents as invoice line items to stripe customer.
func (service *Service) applyProjectRecords(ctx context.Context, records []ProjectRecord) (err error) {
If stripe customer has no line items invoice creation will fail. This error is checked and skipped for loop not to break for customers with no projects or in case of retrial skipped customers with successfully created invoices when previous loop failed.
```go
// createInvoice creates invoice for stripe customer. Returns nil error if there are no