def testSimpleRequiresChoices(self): v = config_value("SHOP", "rc1") self.assertEqual(v, ["c1"]) g = config_get_group("req2") keys = [cfg.key for cfg in g] self.assertEqual(keys, ["c1", "c3"]) c = config_get("SHOP", "rc1") c.update(["c1", "c2"]) g = config_get_group("req2") keys = [cfg.key for cfg in g] self.assertEqual(keys, ["c1", "c2", "c3"])
def testRequiresValue(self): v = config_value("SHOP", "valchoices") self.assertEqual(v, ["foo"]) g = config_get_group("reqval") keys = [cfg.key for cfg in g] self.assertEqual(keys, ["c1", "c3"]) c = config_get("SHOP", "valchoices") c.update(["foo", "bar"]) g = config_get_group("reqval") keys = [cfg.key for cfg in g] self.assertEqual(keys, ["c1", "c2", "c3"])
def one_step(request): # Check that items are in stock cart = Cart.objects.from_request(request) if cart.not_enough_stock(): return HttpResponseRedirect(reverse("satchmo_cart")) payment_module = config_get_group("PAYMENT_AUTOSUCCESS") # First verify that the customer exists try: contact = Contact.objects.from_request(request, create=False) except Contact.DoesNotExist: url = lookup_url(payment_module, "satchmo_checkout-step1") return HttpResponseRedirect(url) # Verify we still have items in the cart if cart.numItems == 0: template = lookup_template(payment_module, "checkout/empty_cart.html") return render(request, template) # Create a new order newOrder = Order(contact=contact) pay_ship_save(newOrder, cart, contact, shipping="", discount="") request.session["orderID"] = newOrder.id newOrder.add_status(status="Pending", notes="Order successfully submitted") record_payment(newOrder, payment_module, amount=newOrder.balance) success = lookup_url(payment_module, "satchmo_checkout-success") return HttpResponseRedirect(success)
def payment_label(value): """convert a payment key into its translated text""" payments = config_get("PAYMENT", "MODULES") for mod in payments.value: config = config_get_group(mod) if config.KEY.value == value: return translation.ugettext(str(config.LABEL)) return value.capitalize()
def pay_ship_info(request): # Check that items are in stock cart = Cart.objects.from_request(request) if cart.not_enough_stock(): return HttpResponseRedirect(reverse("satchmo_cart")) return payship.base_pay_ship_info( request, config_get_group("PAYMENT_PAYPAL"), payship.simple_pay_ship_process_form, "checkout/paypal/pay_ship.html", )
def configure_api(): """ Configure PayPal api """ payment_module = config_get_group("PAYMENT_PAYPAL") if payment_module.LIVE.value: MODE = "live" CLIENT = payment_module.CLIENT_ID.value SECRET = payment_module.SECRET_KEY.value else: MODE = "sandbox" CLIENT = payment_module.SANDBOX_CLIENT_ID.value SECRET = payment_module.SANDBOX_SECRET_KEY.value paypalrestsdk.configure( {"mode": MODE, "client_id": CLIENT, "client_secret": SECRET} )
def apply_to_order(self, order): """Apply up to the full amount of the balance of this cert to the order. Returns new balance. """ amount = min(order.balance, self.balance) log.info( "applying %s from giftcert #%i [%s] to order #%i [%s]", money_format(amount, order.currency.iso_4217_code), self.id, money_format(self.balance, order.currency.iso_4217_code), order.id, money_format(order.balance, order.currency.iso_4217_code), ) config = config_get_group("PAYMENT_GIFTCERTIFICATE") orderpayment = record_payment(order, config, amount) return self.use(amount, orderpayment=orderpayment)
def configure_api(): """ Configure PayPal api """ payment_module = config_get_group("PAYMENT_PAYPAL") if payment_module.LIVE.value: MODE = "live" CLIENT = payment_module.CLIENT_ID.value SECRET = payment_module.SECRET_KEY.value else: MODE = "sandbox" CLIENT = payment_module.SANDBOX_CLIENT_ID.value SECRET = payment_module.SANDBOX_SECRET_KEY.value paypalrestsdk.configure({ "mode": MODE, "client_id": CLIENT, "client_secret": SECRET })
def config_tax(): TAX_MODULE = config_get("TAX", "MODULE") TAX_MODULE.add_choice(("satchmo.tax.modules.area", _("By Country/Area"))) TAX_GROUP = config_get_group("TAX") _tax_classes = [] ship_default = "" try: for tax in TaxClass.objects.all(): _tax_classes.append((tax.title, tax)) if "ship" in tax.title.lower(): ship_default = tax.title except: log.warn( "Ignoring database error retrieving tax classes - OK if you are in syncdb." ) if ship_default == "" and len(_tax_classes) > 0: ship_default = _tax_classes[0][0] config_register( BooleanValue( TAX_GROUP, "TAX_SHIPPING", description=_("Tax Shipping?"), requires=TAX_MODULE, requiresvalue="satchmo.tax.modules.area", default=False, ) ) config_register( StringValue( TAX_GROUP, "TAX_CLASS", description=_("TaxClass for shipping"), help_text=_("Select a TaxClass that should be applied for shipments."), default=ship_default, choices=_tax_classes, ) )
def config_tax(): TAX_MODULE = config_get("TAX", "MODULE") TAX_MODULE.add_choice(("satchmo.tax.modules.area", _("By Country/Area"))) TAX_GROUP = config_get_group("TAX") _tax_classes = [] ship_default = "" try: for tax in TaxClass.objects.all(): _tax_classes.append((tax.title, tax)) if "ship" in tax.title.lower(): ship_default = tax.title except: log.warn( "Ignoring database error retrieving tax classes - OK if you are in syncdb." ) if ship_default == "" and len(_tax_classes) > 0: ship_default = _tax_classes[0][0] config_register( BooleanValue( TAX_GROUP, "TAX_SHIPPING", description=_("Tax Shipping?"), requires=TAX_MODULE, requiresvalue="satchmo.tax.modules.area", default=False, )) config_register( StringValue( TAX_GROUP, "TAX_CLASS", description=_("TaxClass for shipping"), help_text=_( "Select a TaxClass that should be applied for shipments."), default=ship_default, choices=_tax_classes, ))
def confirm_info(request): # Check that items are in stock cart = Cart.objects.from_request(request) if cart.not_enough_stock(): return HttpResponseRedirect(reverse("satchmo_cart")) payment_module = config_get_group("PAYMENT_PAYPAL") # Get the order, # if there is no order, return to checkout step 1 try: order = Order.objects.from_request(request) except Order.DoesNotExist: url = lookup_url(payment_module, "satchmo_checkout-step1") return HttpResponseRedirect(url) # Check that the cart has items in it. if cart.numItems == 0: template = lookup_template(payment_module, "checkout/empty_cart.html") return render(request, template) # Check if the order is still valid if not order.validate(request): context = {"message": _("Your order is no longer valid.")} return render(request, "shop_404.html", context) # Set environment if payment_module.LIVE.value: environment = "production" else: environment = "sandbox" context = { "order": order, "environment": environment, "PAYMENT_LIVE": payment_live(payment_module), } template = lookup_template(payment_module, "checkout/paypal/confirm.html") return render(request, template, context)
def balance_remaining(request): """Allow the user to pay the remaining balance.""" order = None orderid = request.session.get("orderID") if orderid: try: order = Order.objects.get(pk=orderid) except Order.DoesNotExist: # TODO: verify user against current user pass if not order: url = reverse("satchmo_checkout-step1") return HttpResponseRedirect(url) if request.method == "POST": new_data = request.POST.copy() form = PaymentMethodForm(new_data, order=order) if form.is_valid(): data = form.cleaned_data modulename = data["paymentmethod"] if not modulename.startswith("PAYMENT_"): modulename = "PAYMENT_" + modulename paymentmodule = config_get_group(modulename) url = lookup_url(paymentmodule, "satchmo_checkout-step2") return HttpResponseRedirect(url) else: form = PaymentMethodForm(order=order) ctx = { "form": form, "order": order, "paymentmethod_ct": len(config_value("PAYMENT", "MODULES")), } return render(request, "checkout/balance_remaining.html", ctx)
from django.utils.translation import ugettext_lazy as _ from satchmo.configuration.functions import ( config_get, config_get_group, config_register, ) from satchmo.configuration.values import DecimalValue, BooleanValue TAX_MODULE = config_get("TAX", "MODULE") TAX_MODULE.add_choice(("satchmo.tax.modules.percent", _("Percent Tax"))) TAX_GROUP = config_get_group("TAX") config_register( DecimalValue( TAX_GROUP, "PERCENT", description=_("Percent tax"), requires=TAX_MODULE, requiresvalue="satchmo.tax.modules.percent", ) ) config_register( BooleanValue( TAX_GROUP, "TAX_SHIPPING", description=_("Tax Shipping?"), requires=TAX_MODULE, requiresvalue="satchmo.tax.modules.percent",
def create_payment(request, retries=0): """Call the /v1/payments/payment REST API with your client ID and secret and your payment details to create a payment ID. Return the payment ID to the client as JSON. """ if request.method != "POST": raise Http404 # Get the order, if there is no order, return to checkout step 1 try: order = Order.objects.from_request(request) except Order.DoesNotExist: raise Http404 # Contact PayPal to create the payment configure_api() payment_module = config_get_group("PAYMENT_PAYPAL") site = Site.objects.get_current() data = { "intent": "order", "payer": { "payment_method": "paypal" }, "redirect_urls": { "return_url": lookup_url(payment_module, "paypal:satchmo_checkout-success", include_server=True), "cancel_url": lookup_url(payment_module, "satchmo_checkout-step1", include_server=True), }, "transactions": [{ "amount": { "currency": order.currency.iso_4217_code, "total": str(order.total), "details": { "subtotal": str(order.sub_total - order.discount), "tax": str(order.tax), "shipping": str(order.shipping_cost), "shipping_discount": str(order.shipping_discount), }, }, "description": "Your {site} order.".format(site=site.name), "invoice_number": str(order.id), "payment_options": { "allowed_payment_method": "UNRESTRICTED" }, "item_list": { "items": [{ "name": item.product.name, "description": item.product.meta, "quantity": item.quantity, "currency": order.currency.iso_4217_code, "price": "{price:.2f}".format( price=(item.unit_price - (item.discount / item.quantity))), "tax": str(item.unit_tax), "sku": item.product.sku, } for item in order.orderitem_set.all()], "shipping_address": { "recipient_name": order.ship_addressee, "line1": order.ship_street1, "line2": order.ship_street2, "city": order.ship_city, "country_code": order.ship_country.iso2_code, "postal_code": order.ship_postal_code, "state": order.ship_state, }, "shipping_method": order.shipping_description, }, }], } # Send it to PayPal payment = paypalrestsdk.Payment(data) if payment.create(): order.notes += _("--- Paypal Payment Created ---") + "\n" order.notes += str(timezone.now()) + "\n" order.notes += pprint.pformat(payment.to_dict()) + "\n\n" order.freeze() order.save() # Create a pending payment in our system order_payment = create_pending_payment(order, payment_module) order_payment.transaction_id = payment["id"] order_payment.save() # Return JSON to client return JsonResponse(payment.to_dict(), status=201) else: subject = "PayPal API error" message = "\n".join("{key}: {value}".format(key=key, value=value) for key, value in payment.error.items()) # Add the posted data message += "\n\n" + pprint.pformat(data) + "\n" mail_admins(subject, message) log.error(payment.error) log.error(pprint.pformat(data)) if payment.error["name"] == "VALIDATION_ERROR": data = payment.error["details"] else: data = { "message": _("""Something went wrong with your PayPal payment. Please try again. If the problem persists, please contact us.""") } # Because we are returning a list of dicts, we must mark it as unsafe return JsonResponse(data, status=400, safe=False)
def setUp(self): self.dummy = config_get_group("PAYMENT_DUMMY")
def webhook(request): """Handle PayPal webhooks. The PayPal REST APIs use webhooks for event notification. Webhooks are HTTP callbacks that receive notification messages for events. https://developer.paypal.com/docs/api/webhooks/v1/ We only care about the following event types CUSTOMER.DISPUTE.CREATED A customer dispute is created. CUSTOMER.DISPUTE.RESOLVED A customer dispute is resolved. CUSTOMER.DISPUTE.UPDATED A customer dispute is updated. IDENTITY.AUTHORIZATION-CONSENT.REVOKED A risk dispute is created. PAYMENT-NETWORKS.ALTERNATIVE-PAYMENT.COMPLETED Webhook event payload to send for Alternative Payment Completion. PAYMENT.SALE.COMPLETED A sale completes. PAYMENT.SALE.DENIED The state of a sale changes from pending to denied. PAYMENT.SALE.PENDING The state of a sale changes to pending. PAYMENT.SALE.REFUNDED A merchant refunds a sale. PAYMENT.SALE.REVERSED PayPal reverses a sale. """ if request.method != "POST": raise Http404 data = json.loads(request.body.decode("utf-8")) payment_module = config_get_group("PAYMENT_PAYPAL") if payment_module.LIVE.value: webhook_id = payment_module.WEBHOOK_ID.value else: webhook_id = payment_module.SANDBOX_WEBHOOK_ID.value transmission_id = request.META["HTTP_PAYPAL_TRANSMISSION_ID"] timestamp = request.META["HTTP_PAYPAL_TRANSMISSION_TIME"] event_body = request.body.decode("utf-8") cert_url = request.META["HTTP_PAYPAL_CERT_URL"] actual_signature = request.META["HTTP_PAYPAL_TRANSMISSION_SIG"] auth_algo = request.META["HTTP_PAYPAL_AUTH_ALGO"] verified = WebhookEvent.verify( transmission_id, timestamp, webhook_id, event_body, cert_url, actual_signature, auth_algo, ) if verified is False: subject = "PayPal API webhook verification error" message = pprint.pformat(data) mail_admins(subject, message) log.error("PayPal API webhook verification error") log.error(pprint.pformat(data)) return JsonResponse({"error": "PayPal API webhook verification error"}, status=400) event_type = data.get("event_type") if event_type is not None: if event_type.startswith("CUSTOMER.DISPUTE") or event_type.startswith( "RISK.DISPUTE"): # Customer dispute # Add a note, but don't do anything else order_payment = get_object_or_404( OrderPayment, transaction_id=data["resource"]["disputed_transactions"][0] ["seller_transaction_id"], ) order = order_payment.order order.notes += "\n" + _("--- Paypal Customer Dispute ---") + "\n" order.add_status( status="Customer Dispute", notes=data["resource"]["messages"][0]["content"], ) elif event_type == "PAYMENTS.PAYMENT.CREATED": try: order_id = data["resource"]["transactions"][0][ "invoice_number"] except KeyError: response_data = {"status": "not found"} return JsonResponse(data=response_data, status=404) order = get_object_or_404(Order, id=order_id) # Set Order to Processing order.add_status(status="Payment Created", notes=_("PayPal payment created. Thank you.")) elif event_type.endswith("COMPLETED"): # Payment complete order = get_object_or_404(Order, id=data["resource"]["invoice_number"]) if order.order_states.filter( status__status="Processing").exists() is False: order.notes += "\n" + _( "--- Paypal Payment Complete ---") + "\n" # Update order payment values order_payment = get_object_or_404( OrderPayment, transaction_id=data["resource"]["parent_payment"]) order_payment.amount = data["resource"]["amount"]["total"] order_payment.transaction_id = data["id"] order_payment.save() # Set Order to Processing order.add_status(status="Processing", notes=_("Paid by PayPal. Thank you.")) else: log.info("Already processed {payment}".format( payment=data["resource"]["parent_payment"])) elif event_type.endswith("DENIED"): order_payment = get_object_or_404( OrderPayment, transaction_id=data["resource"]["parent_payment"]) order = order_payment.order order.notes += "\n" + _("--- Paypal Payment Denied ---") + "\n" order.add_status(status="Denied", notes=_("PayPal Payment Denied.")) elif event_type.endswith("PENDING"): order_payment = get_object_or_404( OrderPayment, transaction_id=data["resource"]["parent_payment"]) order = order_payment.order order.notes += "\n" + _("--- Paypal Payment Pending ---") + "\n" order.add_status(status="Pending", notes=_("PayPal Payment Pending.")) elif event_type.endswith("REFUNDED"): order = get_object_or_404(Order, id=data["resource"]["invoice_number"]) order.notes += "\n" + _("--- Paypal Payment Refunded ---") + "\n" order.add_status(status="Refunded", notes=_("PayPal Refund")) OrderRefund.objects.create( payment="PAYPAL", order=order, amount=Decimal(data["resource"]["amount"]["total"]), exchange_rate=order.exchange_rate, transaction_id=data["id"], ) elif event_type == "PAYMENT.SALE.REVERSED": order_payment = get_object_or_404( OrderPayment, transaction_id=data["resource"]["id"]) order = order_payment.order order.notes += "\n" + _("--- Paypal Payment Reversed ---") + "\n" order.add_status(status="Reversed", notes=_("PayPal Payment Reversed.")) OrderPayment.objects.create( order=order, amount=data["resource"]["amount"] ["total"], # Minus amount for reversal exchange_rate=order.exchange_rate, payment="PAYPAL", transaction_id=data["id"], ) else: order_payment = get_object_or_404( OrderPayment, transaction_id=data["resource"]["parent_payment"]) order = order_payment.order order.notes += ("\n" + _( "--- Paypal {event_type} ---".format(event_type=event_type)) + "\n") order.notes += str(timezone.now()) + "\n" order.notes += pprint.pformat(data) + "\n" order.save() response_data = {"status": "success"} return JsonResponse(data=response_data, status=201)
def cron_rebill(request=None): """Rebill customers with expiring recurring subscription products This can either be run via a url with GET key authentication or directly from a shell script. """ # TODO: support re-try billing for failed transactions if request is not None: if not config_value("PAYMENT", "ALLOW_URL_REBILL"): return bad_or_missing(request, _("Feature is not enabled.")) if "key" not in request.GET or request.GET["key"] != config_value( "PAYMENT", "CRON_KEY"): return HttpResponse("Authentication Key Required") expiring_subscriptions = OrderItem.objects.filter( expire_date__gte=datetime.now()).order_by("order", "id", "expire_date") for item in expiring_subscriptions: if ( item.product.is_subscription ): # TODO - need to add support for products with trial but non-recurring if (item.product.subscriptionproduct.recurring_times and item.product.subscriptionproduct.recurring_times + item.product.subscriptionproduct.get_trial_terms().count() == OrderItem.objects.filter(order=item.order, product=item.product).count()): continue if item.expire_date == datetime.date( datetime.now()) and item.completed: if (item.id == OrderItem.objects.filter( product=item.product, order=item.order).order_by("-id")[0].id): # bill => add orderitem, recalculate total, porocess card new_order_item = OrderItem( order=item.order, product=item.product, quantity=item.quantity, unit_price=item.unit_price, line_item_price=item.line_item_price, ) # if product is recurring, set subscription end if item.product.subscriptionproduct.recurring: new_order_item.expire_date = ( item.product.subscriptionproduct.calc_expire_date( )) # check if product has 2 or more trial periods and if the last one paid was a trial or a regular payment. ordercount = item.order.orderitem_set.all().count() if (item.product.subscriptionproduct.get_trial_terms( ).count() > 1 and item.unit_price == item.product.subscriptionproduct. get_trial_terms(ordercount - 1).price): new_order_item.unit_price = item.product.subscriptionproduct.get_trial.terms( ordercount).price new_order_item.line_item_price = ( new_order_item.quantity * new_order_item.unit_price) new_order_item.expire_date = item.product.subscriptionproduct.get_trial_terms( ordercount).calc_expire_date() new_order_item.save() item.order.recalculate_total() # if new_order_item.product.subscriptionproduct.is_shippable == 3: # item.order.total = item.order.total - item.order.shipping_cost # item.order.save() payments = item.order.payments.all()[0] # list of ipn based payment modules. Include processors that use 3rd party recurring billing. ipn_based = ["PAYPAL"] if payments.payment not in ipn_based and item.order.balance > 0: # run card # Do the credit card processing here & if successful, execute the success_handler payment_module = config_get_group("PAYMENT_%s" % payments.payment) credit_processor = payment_module.MODULE.load_module( "processor") processor = credit_processor.PaymentProcessor( payment_module) processor.prepareData(item.order) results, reason_code, msg = processor.process() log.info( """Processing %s recurring transaction with %s Order #%i Results=%s Response=%s Reason=%s""", payment_module.LABEL.value, payment_module.KEY.value, item.order.id, results, reason_code, msg, ) if results: # success handler item.order.add_status( status="Pending", notes= "Subscription Renewal Order successfully submitted", ) new_order_item.completed = True new_order_item.save() orderpayment = OrderPayment( order=item.order, amount=item.order.balance, payment=str(payment_module.KEY.value), ) orderpayment.save() return HttpResponse()
from django import http from django.shortcuts import render from satchmo.configuration.functions import config_get_group from satchmo.shop.models import Order from satchmo.payment.utils import get_or_create_order from satchmo.payment.views import confirm, payship from satchmo.utils.dynamic import lookup_url from .forms import GiftCertCodeForm, GiftCertPayShipForm from .models import GiftCertificate, GIFTCODE_KEY gc = config_get_group("PAYMENT_GIFTCERTIFICATE") def giftcert_pay_ship_process_form(request, contact, working_cart, payment_module): if request.method == "POST": new_data = request.POST.copy() form = GiftCertPayShipForm(request, payment_module, new_data) if form.is_valid(): data = form.cleaned_data # Create a new order. newOrder = get_or_create_order(request, working_cart, contact, data) newOrder.add_variable(GIFTCODE_KEY, data["giftcode"]) request.session["orderID"] = newOrder.id url = None
def cron_rebill(request=None): """Rebill customers with expiring recurring subscription products This can either be run via a url with GET key authentication or directly from a shell script. """ # TODO: support re-try billing for failed transactions if request is not None: if not config_value("PAYMENT", "ALLOW_URL_REBILL"): return bad_or_missing(request, _("Feature is not enabled.")) if "key" not in request.GET or request.GET["key"] != config_value( "PAYMENT", "CRON_KEY" ): return HttpResponse("Authentication Key Required") expiring_subscriptions = OrderItem.objects.filter( expire_date__gte=datetime.now() ).order_by("order", "id", "expire_date") for item in expiring_subscriptions: if ( item.product.is_subscription ): # TODO - need to add support for products with trial but non-recurring if ( item.product.subscriptionproduct.recurring_times and item.product.subscriptionproduct.recurring_times + item.product.subscriptionproduct.get_trial_terms().count() == OrderItem.objects.filter( order=item.order, product=item.product ).count() ): continue if item.expire_date == datetime.date(datetime.now()) and item.completed: if ( item.id == OrderItem.objects.filter(product=item.product, order=item.order) .order_by("-id")[0] .id ): # bill => add orderitem, recalculate total, porocess card new_order_item = OrderItem( order=item.order, product=item.product, quantity=item.quantity, unit_price=item.unit_price, line_item_price=item.line_item_price, ) # if product is recurring, set subscription end if item.product.subscriptionproduct.recurring: new_order_item.expire_date = ( item.product.subscriptionproduct.calc_expire_date() ) # check if product has 2 or more trial periods and if the last one paid was a trial or a regular payment. ordercount = item.order.orderitem_set.all().count() if ( item.product.subscriptionproduct.get_trial_terms().count() > 1 and item.unit_price == item.product.subscriptionproduct.get_trial_terms( ordercount - 1 ).price ): new_order_item.unit_price = item.product.subscriptionproduct.get_trial.terms( ordercount ).price new_order_item.line_item_price = ( new_order_item.quantity * new_order_item.unit_price ) new_order_item.expire_date = item.product.subscriptionproduct.get_trial_terms( ordercount ).calc_expire_date() new_order_item.save() item.order.recalculate_total() # if new_order_item.product.subscriptionproduct.is_shippable == 3: # item.order.total = item.order.total - item.order.shipping_cost # item.order.save() payments = item.order.payments.all()[0] # list of ipn based payment modules. Include processors that use 3rd party recurring billing. ipn_based = ["PAYPAL"] if payments.payment not in ipn_based and item.order.balance > 0: # run card # Do the credit card processing here & if successful, execute the success_handler payment_module = config_get_group( "PAYMENT_%s" % payments.payment ) credit_processor = payment_module.MODULE.load_module( "processor" ) processor = credit_processor.PaymentProcessor(payment_module) processor.prepareData(item.order) results, reason_code, msg = processor.process() log.info( """Processing %s recurring transaction with %s Order #%i Results=%s Response=%s Reason=%s""", payment_module.LABEL.value, payment_module.KEY.value, item.order.id, results, reason_code, msg, ) if results: # success handler item.order.add_status( status="Pending", notes="Subscription Renewal Order successfully submitted", ) new_order_item.completed = True new_order_item.save() orderpayment = OrderPayment( order=item.order, amount=item.order.balance, payment=str(payment_module.KEY.value), ) orderpayment.save() return HttpResponse()
def contact_info(request, **kwargs): """View which collects demographic information from customer.""" # First verify that the cart exists and has items tempCart = Cart.objects.from_request(request) if tempCart.numItems == 0: return render(request, "checkout/empty_cart.html") init_data = {} shop = Config.objects.get_current() if request.user.email: init_data["email"] = request.user.email if request.user.first_name: init_data["first_name"] = request.user.first_name if request.user.last_name: init_data["last_name"] = request.user.last_name try: contact = Contact.objects.from_request(request, create=False) except Contact.DoesNotExist: contact = None # Check that items are in stock cart = Cart.objects.from_request(request) if cart.not_enough_stock(): return http.HttpResponseRedirect(reverse("satchmo_cart")) if request.method == "POST": new_data = request.POST.copy() if not tempCart.is_shippable: new_data["copy_address"] = True form = PaymentContactInfoForm( new_data, shop=shop, contact=contact, shippable=tempCart.is_shippable, initial=init_data, cart=tempCart, ) if form.is_valid(): if contact is None and request.user and request.user.is_authenticated: contact = Contact(user=request.user) custID = form.save(contact=contact) request.session[CUSTOMER_ID] = custID # TODO - Create an order here and associate it with a session modulename = new_data["paymentmethod"] if not modulename.startswith("PAYMENT_"): modulename = "PAYMENT_" + modulename paymentmodule = config_get_group(modulename) url = lookup_url(paymentmodule, "satchmo_checkout-step2") return http.HttpResponseRedirect(url) else: log.debug("Form errors: %s", form.errors) else: if contact: # If a person has their contact info, make sure we populate it in the form for item in list(contact.__dict__.keys()): init_data[item] = getattr(contact, item) if contact.shipping_address: for item in list(contact.shipping_address.__dict__.keys()): init_data["ship_" + item] = getattr(contact.shipping_address, item) if contact.billing_address: for item in list(contact.billing_address.__dict__.keys()): init_data[item] = getattr(contact.billing_address, item) if contact.primary_phone: init_data["phone"] = contact.primary_phone.phone else: # Allow them to login from this page. request.session.set_test_cookie() init_data["copy_address"] = True form = PaymentContactInfoForm( shop=shop, contact=contact, shippable=tempCart.is_shippable, initial=init_data, cart=tempCart, ) if shop.in_country_only: only_country = shop.sales_country else: only_country = None context = { "form": form, "country": only_country, "paymentmethod_ct": len(form.fields["paymentmethod"].choices), } return render(request, "checkout/form.html", context)
from django.utils.translation import ugettext_lazy as _ from satchmo.configuration.functions import ( config_get, config_get_group, config_register_list, ) from satchmo.configuration.values import DecimalValue, StringValue SHIP_MODULES = config_get("SHIPPING", "MODULES") SHIP_MODULES.add_choice(("satchmo.shipping.modules.flat", _("Flat rate"))) SHIPPING_GROUP = config_get_group("SHIPPING") config_register_list( DecimalValue( SHIPPING_GROUP, "FLAT_RATE", description=_("Flat shipping"), requires=SHIP_MODULES, requiresvalue="satchmo.shipping.modules.flat", default="4.00", ), StringValue( SHIPPING_GROUP, "FLAT_SERVICE", description=_("Flat Shipping Service"), help_text=_("Shipping service used with Flat rate shipping"), requires=SHIP_MODULES, requiresvalue="satchmo.shipping.modules.flat", default="U.S. Mail", ), StringValue(
from django.urls import path from satchmo.configuration.functions import config_get_group, config_get, config_value from satchmo.configuration.values import SettingNotSet from satchmo.payment.views import contact, balance, cron import logging log = logging.getLogger(__name__) config = config_get_group("PAYMENT") urlpatterns = [ path("", contact.contact_info_view, {}, "satchmo_checkout-step1"), path( "custom/charge/<int:orderitem_id>/", balance.charge_remaining, {}, "satchmo_charge_remaining", ), path( "custom/charge/", balance.charge_remaining_post, {}, "satchmo_charge_remaining_post", ), path( "balance/<int:order_id>/", balance.balance_remaining_order, {}, "satchmo_balance_remaining_order", ),
from django.utils.translation import ugettext_lazy as _ from satchmo.configuration.functions import ( config_get, config_get_group, config_register_list, ) from satchmo.configuration.values import DecimalValue, StringValue SHIP_MODULES = config_get("SHIPPING", "MODULES") # No need to add the choice, since it is in by default # SHIP_MODULES.add_choice(('satchmo.shipping.modules.per', _('Per piece'))) SHIPPING_GROUP = config_get_group("SHIPPING") config_register_list( DecimalValue( SHIPPING_GROUP, "PER_RATE", description=_("Per item price"), requires=SHIP_MODULES, requiresvalue="satchmo.shipping.modules.per", default="4.00", ), StringValue( SHIPPING_GROUP, "PER_SERVICE", description=_("Per Item Shipping Service"), help_text=_("Shipping service used with per item shipping"),
def create_payment(request, retries=0): """Call the /v1/payments/payment REST API with your client ID and secret and your payment details to create a payment ID. Return the payment ID to the client as JSON. """ if request.method != "POST": raise Http404 # Get the order, if there is no order, return to checkout step 1 try: order = Order.objects.from_request(request) except Order.DoesNotExist: raise Http404 # Contact PayPal to create the payment configure_api() payment_module = config_get_group("PAYMENT_PAYPAL") site = Site.objects.get_current() data = { "intent": "order", "payer": {"payment_method": "paypal"}, "redirect_urls": { "return_url": lookup_url( payment_module, "paypal:satchmo_checkout-success", include_server=True ), "cancel_url": lookup_url( payment_module, "satchmo_checkout-step1", include_server=True ), }, "transactions": [ { "amount": { "currency": order.currency.iso_4217_code, "total": str(order.total), "details": { "subtotal": str(order.sub_total), "tax": str(order.tax), "shipping": str(order.shipping_cost), "shipping_discount": str(order.shipping_discount), }, }, "description": "Your {site} order.".format(site=site.name), "invoice_number": str(order.id), "payment_options": {"allowed_payment_method": "UNRESTRICTED"}, "item_list": { "items": [ { "name": item.product.name, "description": item.product.meta, "quantity": item.quantity, "currency": order.currency.iso_4217_code, "price": str(item.unit_price), "tax": str(item.unit_tax), "sku": item.product.sku, } for item in order.orderitem_set.all() ], "shipping_address": { "recipient_name": order.ship_addressee, "line1": order.ship_street1, "line2": order.ship_street2, "city": order.ship_city, "country_code": order.ship_country.iso2_code, "postal_code": order.ship_postal_code, "state": order.ship_state, }, "shipping_method": order.shipping_description, }, } ], } # Send it to PayPal payment = paypalrestsdk.Payment(data) if payment.create(): order.notes += _("--- Paypal Payment Created ---") + "\n" order.notes += str(timezone.now()) + "\n" order.notes += pprint.pformat(payment.to_dict()) + "\n" order.freeze() order.save() # Create a pending payment in our system order_payment = create_pending_payment(order, payment_module) order_payment.transaction_id = payment["id"] order_payment.save() # Return JSON to client return JsonResponse(payment.to_dict(), status=201) else: subject = "PayPal API error" message = "\n".join( "{key}: {value}".format(key=key, value=value) for key, value in payment.error.items() ) mail_admins(subject, message) log.error(payment.error) log.error(pprint.pformat(data)) if payment.error["name"] == "VALIDATION_ERROR": data = payment.error["details"] else: data = { "message": _( """Something went wrong with your PayPal payment. Please try again. If the problem persists, please contact us.""" ) } # Because we are returning a list of dicts, we must mark it as unsafe return JsonResponse(data, status=400, safe=False)
from django import http from django.contrib.sites.models import Site from django.shortcuts import render from satchmo.configuration.functions import config_get_group from satchmo.shop.models import Order from satchmo.payment.utils import get_or_create_order from satchmo.payment.views import confirm, payship from satchmo.utils.dynamic import lookup_url from .forms import GiftCertCodeForm, GiftCertPayShipForm from .models import GiftCertificate, GIFTCODE_KEY gc = config_get_group("PAYMENT_GIFTCERTIFICATE") def giftcert_pay_ship_process_form(request, contact, working_cart, payment_module): if request.method == "POST": new_data = request.POST.copy() form = GiftCertPayShipForm(request, payment_module, new_data) if form.is_valid(): data = form.cleaned_data # Create a new order. newOrder = get_or_create_order(request, working_cart, contact, data) newOrder.add_variable(GIFTCODE_KEY, data["giftcode"]) request.session["orderID"] = newOrder.id url = None
def contact_info(request, **kwargs): """View which collects demographic information from customer.""" # First verify that the cart exists and has items tempCart = Cart.objects.from_request(request) if tempCart.numItems == 0: return render(request, "checkout/empty_cart.html") init_data = {} shop = Config.objects.get_current() if request.user.email: init_data["email"] = request.user.email if request.user.first_name: init_data["first_name"] = request.user.first_name if request.user.last_name: init_data["last_name"] = request.user.last_name try: contact = Contact.objects.from_request(request, create=False) except Contact.DoesNotExist: contact = None # Check that items are in stock cart = Cart.objects.from_request(request) if cart.not_enough_stock(): return http.HttpResponseRedirect(reverse("satchmo_cart")) if request.method == "POST": new_data = request.POST.copy() if not tempCart.is_shippable: new_data["copy_address"] = True form = PaymentContactInfoForm( new_data, shop=shop, contact=contact, shippable=tempCart.is_shippable, initial=init_data, cart=tempCart, ) if form.is_valid(): if contact is None and request.user and request.user.is_authenticated: contact = Contact(user=request.user) custID = form.save(contact=contact) request.session[CUSTOMER_ID] = custID # TODO - Create an order here and associate it with a session modulename = new_data["paymentmethod"] if not modulename.startswith("PAYMENT_"): modulename = "PAYMENT_" + modulename paymentmodule = config_get_group(modulename) url = lookup_url(paymentmodule, "satchmo_checkout-step2") return http.HttpResponseRedirect(url) else: log.debug("Form errors: %s", form.errors) else: if contact: # If a person has their contact info, make sure we populate it in the form for item in list(contact.__dict__.keys()): init_data[item] = getattr(contact, item) if contact.shipping_address: for item in list(contact.shipping_address.__dict__.keys()): init_data["ship_" + item] = getattr( contact.shipping_address, item) if contact.billing_address: for item in list(contact.billing_address.__dict__.keys()): init_data[item] = getattr(contact.billing_address, item) if contact.primary_phone: init_data["phone"] = contact.primary_phone.phone else: # Allow them to login from this page. request.session.set_test_cookie() init_data["copy_address"] = True form = PaymentContactInfoForm( shop=shop, contact=contact, shippable=tempCart.is_shippable, initial=init_data, cart=tempCart, ) if shop.in_country_only: only_country = shop.sales_country else: only_country = None context = { "form": form, "country": only_country, "paymentmethod_ct": len(form.fields["paymentmethod"].choices), } return render(request, "checkout/form.html", context)
from .utils import shasign from django import forms from satchmo.configuration.functions import config_get_group payment_module = config_get_group("PAYMENT_INGENICO") class IngenicoForm(forms.Form): PSPID = forms.CharField(required=True, widget=forms.HiddenInput()) ORDERID = forms.IntegerField(required=True, widget=forms.HiddenInput()) AMOUNT = forms.CharField(required=True, widget=forms.HiddenInput()) CURRENCY = forms.CharField(required=True, widget=forms.HiddenInput()) CN = forms.CharField(required=True, widget=forms.HiddenInput()) EMAIL = forms.EmailField(required=True, widget=forms.HiddenInput()) OWNERADDRESS = forms.CharField(required=False, widget=forms.HiddenInput()) OWNERZIP = forms.CharField(required=False, widget=forms.HiddenInput()) OWNERTOWN = forms.CharField(required=False, widget=forms.HiddenInput()) OWNERCTY = forms.CharField(required=False, widget=forms.HiddenInput()) # Country OWNERTELNO = forms.CharField(required=False, widget=forms.HiddenInput()) # Generate this field SHASIGN = forms.CharField(required=False, widget=forms.HiddenInput()) def __init__(self, *args, **kwargs): super(IngenicoForm, self).__init__(*args, **kwargs) # If Alias / Tokenisation is used if payment_module.ALIAS.value: self.fields["ALIAS"] = forms.CharField( required=True, widget=forms.HiddenInput()
from django.contrib.sessions.backends.db import SessionStore from django.urls import reverse from django.http import HttpResponseRedirect from django.shortcuts import render from django.utils.translation import ugettext as _ from django.views.decorators.csrf import csrf_exempt from satchmo.configuration.functions import config_get_group, config_value from satchmo.payment.utils import record_payment from satchmo.payment.views import payship from satchmo.payment.views.checkout import success as generic_success from satchmo.shop.models import Cart, Order from satchmo.utils import trunc_decimal from satchmo.utils.dynamic import lookup_template payment_module = config_get_group("PAYMENT_WORLDPAY") def pay_ship_info(request): # Check that items are in stock cart = Cart.objects.from_request(request) if cart.not_enough_stock(): return HttpResponseRedirect(reverse("satchmo_cart")) return payship.simple_pay_ship_info( request, payment_module, template="checkout/worldpay/pay_ship.html") def confirm_info(request): "Create form to send to WorldPay" # Check that items are in stock
def webhook(request): """Handle PayPal webhooks. The PayPal REST APIs use webhooks for event notification. Webhooks are HTTP callbacks that receive notification messages for events. https://developer.paypal.com/docs/api/webhooks/v1/ We only care about the following event types CUSTOMER.DISPUTE.CREATED A customer dispute is created. CUSTOMER.DISPUTE.RESOLVED A customer dispute is resolved. CUSTOMER.DISPUTE.UPDATED A customer dispute is updated. IDENTITY.AUTHORIZATION-CONSENT.REVOKED A risk dispute is created. PAYMENT-NETWORKS.ALTERNATIVE-PAYMENT.COMPLETED Webhook event payload to send for Alternative Payment Completion. PAYMENT.SALE.COMPLETED A sale completes. PAYMENT.SALE.DENIED The state of a sale changes from pending to denied. PAYMENT.SALE.PENDING The state of a sale changes to pending. PAYMENT.SALE.REFUNDED A merchant refunds a sale. PAYMENT.SALE.REVERSED PayPal reverses a sale. """ if request.method != "POST": raise Http404 data = json.loads(request.body.decode("utf-8")) payment_module = config_get_group("PAYMENT_PAYPAL") if payment_module.LIVE.value: webhook_id = payment_module.WEBHOOK_ID.value else: webhook_id = payment_module.SANDBOX_WEBHOOK_ID.value transmission_id = request.META["HTTP_PAYPAL_TRANSMISSION_ID"] timestamp = request.META["HTTP_PAYPAL_TRANSMISSION_TIME"] event_body = request.body.decode("utf-8") cert_url = request.META["HTTP_PAYPAL_CERT_URL"] actual_signature = request.META["HTTP_PAYPAL_TRANSMISSION_SIG"] auth_algo = request.META["HTTP_PAYPAL_AUTH_ALGO"] verified = WebhookEvent.verify( transmission_id, timestamp, webhook_id, event_body, cert_url, actual_signature, auth_algo, ) if verified is False: subject = "PayPal API webhook verification error" message = pprint.pformat(data) mail_admins(subject, message) log.error("PayPal API webhook verification error") log.error(pprint.pformat(data)) return JsonResponse( {"error": "PayPal API webhook verification error"}, status=400 ) event_type = data.get("event_type") if event_type is not None: if event_type.startswith("CUSTOMER.DISPUTE"): # Customer dispute # Add a note, but don't do anything else order_payment = get_object_or_404( OrderPayment, transaction_id=data["resource"]["disputed_transactions"][0][ "seller_transaction_id" ], ) order = order_payment.order order.notes += "\n" + _("--- Paypal Customer Dispute ---") + "\n" elif event_type.endswith("COMPLETED"): # Payment complete order = get_object_or_404(Order, id=data["resource"]["invoice_number"]) if order.order_states.filter(status__status="Processing").exists() is False: order.notes += "\n" + _("--- Paypal Payment Complete ---") + "\n" # Update order payment values order_payment = get_object_or_404( OrderPayment, transaction_id=data["resource"]["parent_payment"] ) order_payment.amount = data["resource"]["amount"]["total"] order_payment.transaction_id = data["id"] order_payment.save() # Set Order to Processing order.add_status( status="Processing", notes=_("Paid by PayPal. Thank you.") ) else: log.info( "Already processed {payment}".format( payment=data["resource"]["parent_payment"] ) ) elif event_type.endswith("DENIED"): order_payment = get_object_or_404( OrderPayment, transaction_id=data["resource"]["parent_payment"] ) order = order_payment.order order.notes += "\n" + _("--- Paypal Payment Denied ---") + "\n" order.add_status(status="Denied", notes=_("PayPal Payment Denied.")) elif event_type.endswith("PENDING"): order_payment = get_object_or_404( OrderPayment, transaction_id=data["resource"]["parent_payment"] ) order = order_payment.order order.notes += "\n" + _("--- Paypal Payment Pending ---") + "\n" order.add_status(status="Pending", notes=_("PayPal Payment Pending.")) elif event_type.endswith("REFUNDED"): order = get_object_or_404(Order, id=data["resource"]["invoice_number"]) order.notes += "\n" + _("--- Paypal Payment Refunded ---") + "\n" order.add_status(status="Refunded", notes=_("PayPal Refund")) OrderRefund.objects.create( payment="PAYPAL", order=order, amount=Decimal(data["resource"]["amount"]["total"]), exchange_rate=order.exchange_rate, transaction_id=data["id"], ) elif event_type == "PAYMENT.SALE.REVERSED": order_payment = get_object_or_404( OrderPayment, transaction_id=data["resource"]["id"] ) order = order_payment.order order.notes += "\n" + _("--- Paypal Payment Reversed ---") + "\n" order.add_status(status="Reversed", notes=_("PayPal Payment Reversed.")) OrderPayment.objects.create( order=order, amount=data["resource"]["amount"]["total"], # Minus amount for reversal exchange_rate=order.exchange_rate, payment="PAYPAL", transaction_id=data["id"], ) else: order_payment = get_object_or_404( OrderPayment, transaction_id=data["resource"]["parent_payment"] ) order = order_payment.order order.notes += ( "\n" + _("--- Paypal {event_type} ---".format(event_type=event_type)) + "\n" ) order.notes += str(timezone.now()) + "\n" order.notes += pprint.pformat(data) + "\n" order.save() response_data = {"status": "success"} return JsonResponse(data=response_data, status=201)
# -*- coding: utf-8 -*- from datetime import date from django.http import Http404 from django.test import TestCase, RequestFactory from satchmo.configuration.functions import config_get_group from satchmo.payment.modules.ingenico.utils import shasign from satchmo.payment.modules.ingenico.views import process from satchmo.shop.factories import TestOrderFactory from satchmo.shop.models import Order, OrderRefund payment_module = config_get_group("PAYMENT_INGENICO") class ProcessTest(TestCase): def setUp(self): self.factory = RequestFactory() def test_GET(self): request = self.factory.get("/shop/checkout/ingenico/success/") response = process(request) # Method Not Allowed self.assertEqual(response.status_code, 405) def test_invalid_shasign(self): order = TestOrderFactory() data = {
"""Simple wrapper for standard checkout as implemented in satchmo.payment.views""" from django.urls import reverse from django.http import HttpResponseRedirect from satchmo.configuration.functions import config_get_group from satchmo.payment.views import confirm, payship from satchmo.shop.models import Cart dummy = config_get_group("PAYMENT_DUMMY") def pay_ship_info(request): # Check that items are in stock cart = Cart.objects.from_request(request) if cart.not_enough_stock(): return HttpResponseRedirect(reverse("satchmo_cart")) return payship.credit_pay_ship_info(request, dummy) def confirm_info(request): # Check that items are in stock cart = Cart.objects.from_request(request) if cart.not_enough_stock(): return HttpResponseRedirect(reverse("satchmo_cart")) return confirm.credit_confirm_info(request, dummy)