Payment
Manage payments for invoices and refunds.
Launch Payment
There are two scenarios that will launch a payment.
- Immediate payment for one-off invoices or entitlements paid in advance.
- Scheduled payment according to the invoice’s due date.
It is possible to use both credit and Stripe payment methods in a single payment.
Payment transactions are saved in the payment_transaction
table.
Payment Status
- Pending: The payment has not started yet.
- Processing: The payment is in process.
- Success: The payment was successful.
- Failed: The payment has failed.
Invoice's payment status is stored in invoice.payment_status
, and each payment transaction has its own status.
The invoice.payment_status
will always remain consistent with the latest payment_transaction.status
for the following reasons:
- Credits payment transaction records status should always be
success
. If the entire amount is charged using credits, theinvoice.payment_status
will be marked assuccess
. Otherwise, theinvoice.payment_status
depends on the provider payment status. - Payment transaction made by provider services such as Stripe may fail, so there may be multiple transaction records. The
invoice.payment_status
will be updated whenever the latest transaction record status is updated.
Pipeline
- Check invoice status, payment status, due date and amount.
- Check invoice payment situation by its payment transactions.
- Get buyer's wallets.
- If credits are available, deduct credits first.
- If the entire amount is paid by credits, mark
invoice.payment_status
as Success, and the Payment pipeline ends. - If the amount is paid by credits partly, update
invoice.payment_status
as Processing. - This step will operate tables of
wallet
,payment_transaction
,invoice
, so wrap these operations in a database transaction.
- If the entire amount is paid by credits, mark
- If there is a remaining amount that needs to be paid by the payment provider, call the Stripe payment API.
- Select a valid payment method from the buyer’s wallets.
- Create a new record in table
payment_transaction
with status set to Processing. - Call Stripe payment API and then update
payment_transaction.status
andinvoice.payment_status
according to the API call result. - If an error occurs during this step, mark
invoice.payment_status
as Failed.
Processing Payment Check Cron Job
Payment status needs to be checked regularly:
- Adjust wrong status according to Stripe API to make sure the invoice payment can continue or be retried.
- Count late payments of each organization.
Payment status may be wrong in some scenarios:
- Database operations failed or were interrupted due to bugs or service issues.
- Stripe webhook events missed by Suger.
Pipeline:
- Loop through each organization.
- Get invoices where the payment due date is within the last 3 days.
- Get invoices issued within the last 3 days with a
payment_status
still in Processing.- Find the latest payment transaction of the invoice.
- If there is no payment transaction, mark as failed because it is abnormal.
- If
payment_transaction.status
is not Processing, mark as failed because it is abnormal. - Retrieve the Stripe PaymentIntent of the transaction by Stripe API.
- If
payment_transaction.Info.StripePaymentIntent
is not nil, usePaymentIntent.ID
directly. - Or else search Stripe PaymentIntents by payment transaction id. If the search results are empty or if there are multiple records, mark as failed because it is abnormal.
- If
- Check if PaymentIntent is still Processing. If not, update
payment_transaction.status
andinvoice.payment_status
.
Refund
A successful payment transaction can be refunded multiple times. Refunds will be issued to the original payment method, such as the credits wallet or a bank card via Stripe. The refund process occurs at the transaction level, rather than the invoice level.
Credit refund
- Check the total refunded amount.
- Check the wallet status: refunds can only be processed for wallets that are in active status.
- Refund to the credit wallet from which the payment was deducted. It contains operations on tables of
payment_transaction
andwallet
, so wrap them in a database transaction. - Credit refunds will always succeed.
Stripe payment refund
- Check the total refunded amount.
- Check the wallet status: if the payment method has been removed from wallet, it would have been detached from the customer on Stripe, and the refund may not be able to be processed.
- Get the Stripe PaymentIntent id in the Charge payment transaction record.
- Create a refund transaction record with status Processing.
- Call Stripe refund API and update the refund transaction status according to the API call result. If the API call fails, mark the refund transaction status as Failed.
Processing Refund Check Cron Job
Refund status may be incorrect in some scenarios, such as payment issues, so we need to check refund status regularly.
Pipeline:
- Loop through each organization.
- Get refunds created in the last 3 days of the organization.
- If a Stripe refund transaction is still in Processing, check it.
- Retrieve stripe Refund object by Stripe API.
- If
transaction.Info.StripeRefund
exists, use the refund id and get refund object from Stripe. - If
transaction.Info.StripeRefund
is nil, it means we failed to update the database after calling the Stripe API. Search for Stripe refunds with PaymentIntent id using Stripe API.- Filter the target refund object by transaction id in meta, because one PaymentIntent can have multiple refund objects.
- Check if the Refund is still in Processing, if not, update
payment_transaction.status
.
- If
- Retrieve stripe Refund object by Stripe API.
E2E Test
Buyer
- [Credits only]
- Payment was successful.
- Refund.
- [Credits only]
- The payment failed because there were not enough credits.
- After adding credits, the payment retry was successful.
- [Credits + Card]
- Payment was successful with multi credits and a card.
- Refund.
- [Credits + Card]
- Payment failed with multi credits and a card.
- After add new card, payment retry was successful.
- [Credits + ACH]
- Payment was successful with multi credits and ACH.
- Refund.
- [Credits + ACH]
- Payment failed with multi credits and ACH.