Skip to content

Commit 1328274

Browse files
Maciej KorycinskiIKarbowiak
Maciej Korycinski
authored andcommitted
Add webhooks logic
1 parent 77c9484 commit 1328274

File tree

9 files changed

+554
-16
lines changed

9 files changed

+554
-16
lines changed

Diff for: saleor/graphql/schema.graphql

+8-2
Original file line numberDiff line numberDiff line change
@@ -2750,7 +2750,7 @@ type Mutation {
27502750
exportProducts(input: ExportProductsInput!): ExportProducts
27512751
checkoutAddPromoCode(checkoutId: ID!, promoCode: String!): CheckoutAddPromoCode
27522752
checkoutBillingAddressUpdate(billingAddress: AddressInput!, checkoutId: ID!): CheckoutBillingAddressUpdate
2753-
checkoutComplete(checkoutId: ID!, redirectUrl: String, storeSource: Boolean = false): CheckoutComplete
2753+
checkoutComplete(checkoutId: ID!, paymentData: JSONString, redirectUrl: String, storeSource: Boolean = false): CheckoutComplete
27542754
checkoutCreate(input: CheckoutCreateInput!): CheckoutCreate
27552755
checkoutCustomerAttach(checkoutId: ID!, customerId: ID): CheckoutCustomerAttach
27562756
checkoutCustomerDetach(checkoutId: ID!): CheckoutCustomerDetach
@@ -3063,6 +3063,7 @@ enum OrderEventsEnum {
30633063
EMAIL_SENT
30643064
PAYMENT_AUTHORIZED
30653065
PAYMENT_CAPTURED
3066+
PAYMENT_GATEWAY_NOTIFICATION
30663067
PAYMENT_REFUNDED
30673068
PAYMENT_VOIDED
30683069
PAYMENT_FAILED
@@ -3387,10 +3388,13 @@ type PaymentCapture {
33873388

33883389
enum PaymentChargeStatusEnum {
33893390
NOT_CHARGED
3391+
PENDING
33903392
PARTIALLY_CHARGED
33913393
FULLY_CHARGED
33923394
PARTIALLY_REFUNDED
33933395
FULLY_REFUNDED
3396+
REFUSED
3397+
CANCELLED
33943398
}
33953399

33963400
type PaymentCountableConnection {
@@ -3435,7 +3439,6 @@ type PaymentGateway {
34353439
input PaymentInput {
34363440
gateway: String!
34373441
token: String
3438-
paymentData: JSONString
34393442
amount: Decimal
34403443
billingAddress: AddressInput
34413444
}
@@ -5023,10 +5026,13 @@ enum TransactionError {
50235026

50245027
enum TransactionKind {
50255028
AUTH
5029+
PENDING
50265030
REFUND
5031+
REFUND_ONGOING
50275032
CAPTURE
50285033
VOID
50295034
CONFIRM
5035+
CANCEL
50305036
}
50315037

50325038
union TranslatableItem = ProductTranslatableContent | CollectionTranslatableContent | CategoryTranslatableContent | AttributeTranslatableContent | AttributeValueTranslatableContent | ProductVariantTranslatableContent | PageTranslatableContent | ShippingMethodTranslatableContent | SaleTranslatableContent | VoucherTranslatableContent | MenuItemTranslatableContent

Diff for: saleor/order/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class OrderEvents:
5151
PAYMENT_REFUNDED = "payment_refunded"
5252
PAYMENT_VOIDED = "payment_voided"
5353
PAYMENT_FAILED = "payment_failed"
54+
PAYMENT_GATEWAY_NOTIFICATION = "payment_gateway_notification"
5455

5556
INVOICE_REQUESTED = "invoice_requested"
5657
INVOICE_GENERATED = "invoice_generated"
@@ -80,6 +81,7 @@ class OrderEvents:
8081
(EMAIL_SENT, "The email was sent"),
8182
(PAYMENT_AUTHORIZED, "The payment was authorized"),
8283
(PAYMENT_CAPTURED, "The payment was captured"),
84+
(PAYMENT_GATEWAY_NOTIFICATION, "Notification from payment gateway"),
8385
(PAYMENT_REFUNDED, "The payment was refunded"),
8486
(PAYMENT_VOIDED, "The payment was voided"),
8587
(PAYMENT_FAILED, "The payment was failed"),

Diff for: saleor/order/events.py

+18
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,24 @@ def payment_failed_event(
260260
)
261261

262262

263+
def payment_gateway_notification_event(
264+
*, order: Order, user: UserType, message: str, payment: Payment
265+
) -> OrderEvent:
266+
if not _user_is_valid(user):
267+
user = None
268+
parameters = {"message": message}
269+
270+
if payment:
271+
parameters.update({"gateway": payment.gateway, "payment_id": payment.token})
272+
273+
return OrderEvent.objects.create(
274+
order=order,
275+
type=OrderEvents.PAYMENT_GATEWAY_NOTIFICATION,
276+
user=user,
277+
parameters=parameters,
278+
)
279+
280+
263281
def fulfillment_canceled_event(
264282
*, order: Order, user: UserType, fulfillment: Fulfillment
265283
) -> OrderEvent:

Diff for: saleor/payment/__init__.py

+9
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,15 @@ class TransactionKind:
5959

6060
AUTH = "auth"
6161
CAPTURE = "capture"
62+
CAPTURE_FAILED = "capture_failed"
6263
VOID = "void"
6364
PENDING = "pending"
6465
REFUND = "refund"
6566
REFUND_ONGOING = "refund_ongoing"
67+
REFUND_FAILED = "refund_failed"
68+
REFUND_REVERSED = "refund_reversed"
6669
CONFIRM = "confirm"
70+
CANCEL = "cancel"
6771
# FIXME we could use another status like WAITING_FOR_AUTH for transactions
6872
# Which were authorized, but needs to be confirmed manually by staff
6973
# eg. Braintree with "submit_for_settlement" enabled
@@ -75,6 +79,7 @@ class TransactionKind:
7579
(CAPTURE, "Capture"),
7680
(VOID, "Void"),
7781
(CONFIRM, "Confirm"),
82+
(CANCEL, "Cancel"),
7883
]
7984

8085

@@ -97,6 +102,8 @@ class ChargeStatus:
97102
FULLY_CHARGED = "fully-charged"
98103
PARTIALLY_REFUNDED = "partially-refunded"
99104
FULLY_REFUNDED = "fully-refunded"
105+
REFUSED = "refused"
106+
CANCELLED = "cancelled"
100107

101108
CHOICES = [
102109
(NOT_CHARGED, "Not charged"),
@@ -105,4 +112,6 @@ class ChargeStatus:
105112
(FULLY_CHARGED, "Fully charged"),
106113
(PARTIALLY_REFUNDED, "Partially refunded"),
107114
(FULLY_REFUNDED, "Fully refunded"),
115+
(REFUSED, "Refused"),
116+
(CANCELLED, "Cancelled"),
108117
]

Diff for: saleor/payment/gateways/adyen/plugin.py

+31-12
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import json
2-
from typing import Optional
2+
from typing import List, Optional
33

44
import Adyen
55
from babel.numbers import get_currency_precision
6+
from django.contrib.auth.hashers import make_password
67
from django.core.handlers.wsgi import WSGIRequest
7-
from django.http import HttpResponse, JsonResponse
8+
from django.http import HttpResponse, HttpResponseNotFound, JsonResponse
89
from graphql_relay import from_global_id
910

1011
from ....checkout.models import Checkout
@@ -21,6 +22,7 @@
2122
request_data_for_payment,
2223
request_for_payment_refund,
2324
)
25+
from .webhooks import handle_webhook
2426

2527
GATEWAY_NAME = "Adyen"
2628

@@ -50,7 +52,6 @@ class AdyenGatewayPlugin(BasePlugin):
5052
{"name": "Origin Key", "value": ""},
5153
{"name": "Origin Url", "value": ""},
5254
{"name": "Live", "value": ""},
53-
{"name": "Enable notifications", "value": True},
5455
{"name": "Automatically mark payment as a capture", "value": True},
5556
{"name": "HMAC secret key", "value": ""},
5657
{"name": "Notification user", "value": ""},
@@ -109,8 +110,7 @@ class AdyenGatewayPlugin(BasePlugin):
109110
"type": ConfigurationTypeField.BOOLEAN,
110111
"help_text": (
111112
"Enable the support for processing the Adyen's webhooks. The Saleor "
112-
"webhook url is <your-backend-url>/plugins/mirumee.payments.adyen/"
113-
"webhooks/ "
113+
"webhook url is http(s)://<your-backend-url>/plugins/mirumee.payments.adyen/webhooks/ "
114114
"https://docs.adyen.com/development-resources/webhooks"
115115
),
116116
"label": "Enable notifications",
@@ -134,7 +134,8 @@ class AdyenGatewayPlugin(BasePlugin):
134134
"help_text": (
135135
"Provide secret key generated on Adyen side."
136136
"https://docs.adyen.com/development-resources/webhooks#set-up-notificat"
137-
"ions-in-your-customer-area"
137+
"ions-in-your-customer-area. The Saleor webhook url is "
138+
"http(s)://<your-backend-url>/plugins/mirumee.payments.adyen/webhooks/"
138139
),
139140
"label": "HMAC secret key",
140141
},
@@ -143,7 +144,9 @@ class AdyenGatewayPlugin(BasePlugin):
143144
"help_text": (
144145
"Base User provided on the Adyen side for authenticate incoming "
145146
"notifications. https://docs.adyen.com/development-resources/webhooks#"
146-
"set-up-notifications-in-your-customer-area"
147+
"set-up-notifications-in-your-customer-area "
148+
"The Saleor webhook url is "
149+
"http(s)://<your-backend-url>/plugins/mirumee.payments.adyen/webhooks/"
147150
),
148151
"label": "Notification user",
149152
},
@@ -152,7 +155,9 @@ class AdyenGatewayPlugin(BasePlugin):
152155
"help_text": (
153156
"User password provided on the Adyen side for authenticate incoming "
154157
"notifications. https://docs.adyen.com/development-resources/webhooks#"
155-
"set-up-notifications-in-your-customer-area"
158+
"set-up-notifications-in-your-customer-area "
159+
"The Saleor webhook url is "
160+
"http(s)://<your-backend-url>/plugins/mirumee.payments.adyen/webhooks/"
156161
),
157162
"label": "Notification password",
158163
},
@@ -163,23 +168,28 @@ def __init__(self, *args, **kwargs):
163168
configuration = {item["name"]: item["value"] for item in self.configuration}
164169
self.config = GatewayConfig(
165170
gateway_name=GATEWAY_NAME,
166-
auto_capture=True, # FIXME check this
171+
auto_capture=configuration[
172+
"Automatically mark payment as a capture"
173+
], # FIXME check this
167174
supported_currencies=configuration["Supported currencies"],
168175
connection_params={
169176
"api_key": configuration["API key"],
170177
"merchant_account": configuration["Merchant Account"],
171178
"return_url": configuration["Return Url"],
172179
"origin_key": configuration["Origin Key"],
173180
"origin_url": configuration["Origin Url"],
181+
"live": configuration["Live"],
182+
"webhook_hmac": configuration["HMAC secret key"],
183+
"webhook_user": configuration["Notification user"],
184+
"webhook_user_password": configuration["Notification password"],
174185
},
175186
)
176187
api_key = self.config.connection_params["api_key"]
177188
self.adyen = Adyen.Adyen(xapikey=api_key)
178189

179190
def webhook(self, request: WSGIRequest, path: str, previous_value) -> HttpResponse:
180-
181-
print(request.body)
182-
return HttpResponse("[accepted]")
191+
config = self._get_gateway_config()
192+
return handle_webhook(request, config)
183193

184194
def _get_gateway_config(self) -> GatewayConfig:
185195
return self.config
@@ -238,6 +248,15 @@ def process_payment(
238248
raw_response=result.message,
239249
)
240250

251+
@classmethod
252+
def _update_config_items(
253+
cls, configuration_to_update: List[dict], current_config: List[dict]
254+
):
255+
super()._update_config_items(configuration_to_update, current_config)
256+
for item in current_config:
257+
if item.get("name") == "Notification password":
258+
item["value"] = make_password(item["value"])
259+
241260
@require_active_plugin
242261
def get_payment_config(self, previous_value):
243262
return []

Diff for: saleor/payment/gateways/adyen/utils.py

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
logger = logging.getLogger(__name__)
1616

1717

18+
def convert_adyen_price_format(value: str, currency: str):
19+
value = Decimal(value)
20+
precision = get_currency_precision(currency)
21+
number_places = Decimal(10) ** -precision
22+
return value * number_places
23+
24+
1825
def get_price_amount(value: Decimal, currency: str):
1926
"""Adyen doesn't use values with comma.
2027

0 commit comments

Comments
 (0)