Skip to main content

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, the invoice.payment_status will be marked as success. Otherwise, the invoice.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 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 and invoice.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, use PaymentIntent.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.
    • Check if PaymentIntent is still Processing. If not, update payment_transaction.status and invoice.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 and wallet, 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.

E2E Test

Buyer

  1. [Credits only]
  • Payment was successful.
  • Refund.
  1. [Credits only]
  • The payment failed because there were not enough credits.
  • After adding credits, the payment retry was successful.
  1. [Credits + Card]
  • Payment was successful with multi credits and a card.
  • Refund.
  1. [Credits + Card]
  • Payment failed with multi credits and a card.
  • After add new card, payment retry was successful.
  1. [Credits + ACH]
  • Payment was successful with multi credits and ACH.
  • Refund.
  1. [Credits + ACH]
  • Payment failed with multi credits and ACH.