Skip to content

Make PaymentMethods Create Separate PaymentSources by Reference

Craig Weber requested to merge fix_payment_source_collapsing into master

Previously, the behavior of PaymentMethod was to create a single PaymentSource model for the intersection of each SourceType and Order. For a split pay order, this had the effect of collapsing multiple transactions into a single PaymentSource, and thus a single amount_allocated field and reference field. That sometimes left orders in an odd state. For example:

  1. Place $10 order with $5 on CardA and $5 on CardB. Both use SourceType "CreditCard".
    1. CardA succeeds and increments PaymentSource1 amount_allocated by $5 (total is now $5).
    2. CardB is declined.
  2. Retry order, but change distribution to $6 and $4.
    1. CardA's original transaction can not be re-used since the amount changed.
    2. Perform another authorization for the new amount. It succeeds and increments PaymentSource1 amount_allocated by $6 (total is now $11).
    3. CardB also succeeds and increments PaymentSource1 amount_allocated by $4 (total is now $15).

IOW, when retrying an order and recycling a transaction won't work, the total of amount_allocated across an Order PaymentSource's will exceed the Order total. Note: this is technically accurate, but makes it difficult to determine which transactions were actually used to pay for an order when it finally does succeed.

This solution introduces two new things: (1) make a new PaymentSource for each reference number (corresponding to each transaction) and (2) set a PaymentSource's amount_allocated to $0 when it's related transaction is going to be discarded. With that change, here's what will now happen in the same situation as above:

  1. Place $10 order with $5 on CardA and $5 on CardB. Both use SourceType "CreditCard".
    1. CardA succeeds and increments PaymentSource1 amount_allocated by $5.
    2. CardB is declined.
  2. Retry order, but change distribution to $6 and $4.
    1. CardA's original transaction can not be re-used since the amount changed.
    2. Void PaymentSource1 by reseting amount_allocated to $0. (Note: Transaction models are left untouched, so you can still see what the original value was).
    3. Perform another authorization for the new amount on CardA. It succeeds and increments PaymentSource2 amount_allocated by $6.
    4. CardB also succeeds and increments PaymentSource3 amount_allocated by $4.

At the end of this order placement dance, the Order has 3 difference PaymentSources all belonging to the same PaymentSourceType.

  1. PaymentSource1's amount_allocated field was reset to $0, showing that it was not used in the final placement of the order.
  2. PaymentSource{2,3} have amount_allocated fields which sum to the same value as the Order total.
  3. All 4 transactions exist, so as to see their original status and amount, showing the full timeline of the Order.
  4. All PaymentSources have reference fields which correspond to their related transactions.

Upgrade Notes:

  1. Payment method plugins should start supplying the source_id kwarg when instantiating and Complete, Declined, or Consumed payment state. This should be set to the pk of the PaymentSource related to that state. The same applies to the utils shortcut method: mark_payment_method_completed, mark_payment_method_declined, and mark_payment_method_consumed.
  2. Payment method plugins should ensure that the value being sent as the reference kwarg when calling PaymentMethod.get_source uniquely identifies an Authorization transaction. This is generally provided by the payment processor and should match the reference field used when building the Transaction model.
  3. Depending on the specifics of the payment method, overriding the new PaymentMethod.void_existing_payment method may be nessisary.

Merge request reports