1
+ from __future__ import annotations
2
+
1
3
import uuid
2
4
from decimal import Decimal
3
5
from typing import TYPE_CHECKING
4
6
5
- from django .db import IntegrityError , models , transaction
6
- from django .db .models import Max , Sum
7
+ from django .db import models , transaction
8
+ from django .db .models import Sum
7
9
from django .utils .translation import gettext_lazy as _
8
10
9
11
from openforms .plugins .constants import UNIQUE_ID_MAX_LENGTH
14
16
if TYPE_CHECKING :
15
17
from openforms .submissions .models import Submission
16
18
17
- ORDER_ID_START = 100
18
- ORDER_ID_PAD_LENGTH = 6
19
-
20
19
21
- class SubmissionPaymentManager (models .Manager ):
20
+ class SubmissionPaymentManager (models .Manager [ "SubmissionPayment" ] ):
22
21
def create_for (
23
22
self ,
24
- submission : " Submission" ,
23
+ submission : Submission ,
25
24
plugin_id : str ,
26
25
plugin_options : dict ,
27
26
amount : Decimal ,
28
- ):
27
+ ) -> SubmissionPayment :
29
28
assert isinstance (amount , Decimal )
30
29
31
30
# first create without order_id
@@ -36,37 +35,29 @@ def create_for(
36
35
amount = amount ,
37
36
)
38
37
# then update with a unique order_id
39
- while True :
40
- try :
41
- with transaction .atomic ():
42
- payment .order_id = self .get_next_order_id ()
43
- payment .public_order_id = self .create_public_order_id_for (payment )
44
- payment .save (update_fields = ("order_id" , "public_order_id" ))
45
- break
46
- except IntegrityError :
47
- # race condition on unique order_id
48
- continue
49
- return payment
50
38
51
- def get_next_order_id (self ) -> int :
52
- agg = self .aggregate (Max ("order_id" ))
53
- max_order_id = agg ["order_id__max" ] or ORDER_ID_START
54
- return max_order_id + 1
39
+ with transaction .atomic ():
40
+ payment .public_order_id = self .create_public_order_id_for (payment )
41
+ payment .save (update_fields = ("public_order_id" ,))
42
+
43
+ return payment
55
44
56
45
@staticmethod
57
- def create_public_order_id_for (payment : " SubmissionPayment" ) -> str :
46
+ def create_public_order_id_for (payment : SubmissionPayment ) -> str :
58
47
config = GlobalConfiguration .get_solo ()
59
- prefix = config .payment_order_id_prefix
60
- prefix = prefix .replace ("{year}" , str (payment .created .year ))
61
- order_id = str (payment .order_id ).rjust (ORDER_ID_PAD_LENGTH , "0" )
62
- return prefix + order_id
48
+ prefix = config .payment_order_id_prefix .format (year = payment .created .year )
49
+ if prefix :
50
+ prefix = f"{ prefix } _"
51
+ return (
52
+ f"{ prefix } { payment .submission .public_registration_reference } _{ payment .pk } "
53
+ )
63
54
64
55
65
- class SubmissionPaymentQuerySet (models .QuerySet ):
56
+ class SubmissionPaymentQuerySet (models .QuerySet [ "SubmissionPayment" ] ):
66
57
def sum_amount (self ) -> Decimal :
67
58
return self .aggregate (sum_amount = Sum ("amount" ))["sum_amount" ] or Decimal ("0" )
68
59
69
- def get_completed_public_order_ids (self ) -> list [int ]:
60
+ def get_completed_public_order_ids (self ) -> list [str ]:
70
61
return list (
71
62
self .filter (
72
63
status__in = (PaymentStatus .registered , PaymentStatus .completed )
@@ -88,17 +79,16 @@ class SubmissionPayment(models.Model):
88
79
null = True ,
89
80
help_text = _ ("Copy of payment options at time of initializing payment." ),
90
81
)
91
- order_id = models .BigIntegerField (
92
- _ ("Order ID (internal)" ),
93
- unique = True ,
94
- null = True ,
95
- help_text = _ ("Unique tracking across backend" ),
96
- )
82
+ # TODO Django 5.2 Update to a `GeneratedField`
97
83
public_order_id = models .CharField (
98
84
_ ("Order ID" ),
99
- max_length = 32 ,
85
+ max_length = 255 ,
100
86
blank = True ,
101
- help_text = _ ("Order ID stored with payment provider." ),
87
+ help_text = _ (
88
+ "The order ID to be sent to the payment provider. This ID is built by "
89
+ "concatenating an optional global prefix, the submission public reference "
90
+ "and a unique incrementing ID."
91
+ ),
102
92
)
103
93
amount = models .DecimalField (
104
94
_ ("payment amount" ),
@@ -114,7 +104,9 @@ class SubmissionPayment(models.Model):
114
104
default = PaymentStatus .started ,
115
105
help_text = _ ("Status of the payment process in the configured backend." ),
116
106
)
117
- objects = SubmissionPaymentManager .from_queryset (SubmissionPaymentQuerySet )()
107
+ objects : SubmissionPaymentManager = SubmissionPaymentManager .from_queryset (
108
+ SubmissionPaymentQuerySet
109
+ )()
118
110
119
111
class Meta :
120
112
verbose_name = _ ("submission payment details" )
@@ -127,8 +119,8 @@ class Meta:
127
119
)
128
120
]
129
121
130
- def __str__ (self ):
131
- return f"#{ self .order_id } '{ self .get_status_display ()} ' { self .amount } "
122
+ def __str__ (self ) -> str :
123
+ return f"#{ self .public_order_id } '{ self .get_status_display ()} ' { self .amount } "
132
124
133
125
@property
134
126
def form (self ):
0 commit comments