def process_paypal_payment(cd_donation_form): # https://developer.paypal.com/webapps/developer/docs/integration/web/accept-paypal-payment/ access_token = get_paypal_access_token() if not access_token: raise PaymentFailureException('NO_ACCESS_TOKEN') data = { 'intent': 'sale', 'redirect_urls': { 'return_url': settings.PAYPAL_CALLBACK, 'cancel_url': settings.PAYPAL_CANCELLATION, }, 'payer': { 'payment_method': 'paypal' }, 'transactions': [{ 'amount': { 'total': cd_donation_form['amount'], 'currency': 'USD', }, 'description': 'Donation to Free Law Project', }] } r = requests.post('%s/v1/payments/payment' % settings.PAYPAL_ENDPOINT, headers={ 'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % access_token }, data=json.dumps(data)) if r.status_code == 201: # "Created" r_content_as_dict = json.loads(r.content) # Get the redirect value from the 'links' attribute. Links look like: # [{u'href': u'https://api.sandbox.paypal.com/v1/payments/payment/PAY-8BC403022U6413151KIQPC2I', # u'method': u'GET', # u'rel': u'self'}, # {u'href': u'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-6VV58324J9479725S', # u'method': u'REDIRECT', # u'rel': u'approval_url'}, # {u'href': u'https://api.sandbox.paypal.com/v1/payments/payment/PAY-8BC403022U6413151KIQPC2I/execute', # u'method': u'POST', # u'rel': u'execute'} # ] redirect = [ link for link in r_content_as_dict['links'] if link['rel'].lower() == 'approval_url' ][0]['href'] parsed_redirect = urlparse(redirect) token = parse_qs(parsed_redirect.query)['token'][0] response = { 'status': Donation.AWAITING_PAYMENT, 'payment_id': r_content_as_dict.get('id'), 'transaction_id': token, 'redirect': redirect, } logger.info("Created payment in paypal with response: %s", response) return response else: raise PaymentFailureException("UNABLE_TO_MAKE_PAYMENT")
def create_stripe_customer(source: str, email: str) -> StripeObject: """Create a stripe customer so that we can charge this person more than once :param source: The stripe token to use for the creation :param email: The customer's email address :return: A stripe customer object """ stripe.api_key = settings.STRIPE_SECRET_KEY try: return stripe.Customer.create(source=source, email=email) except (stripe.error.CardError, stripe.error.InvalidRequestError) as e: logger.warning("Stripe was unable to create the customer: %s" % e) message = ( "Oops, we had an error with your donation: " "<strong>%s</strong>" % e.json_body["error"]["message"] ) raise PaymentFailureException(message) except APIConnectionError: logger.warning("Unable to connect to stripe to create customer.") raise PaymentFailureException( "Oops. We were unable to connect to our payment provider. " "Please try again. If this error continues, please try again " "later." )
def route_and_process_payment( request, cd_donation_form, cd_user_form, payment_provider, frequency, stripe_redirect_url, payment_type, ): """Routes the donation to the correct payment provider, then normalizes its response. :param request: The WSGI request from Django :param cd_donation_form: The donation form with cleaned data :param cd_user_form: The user form with cleaned data :param payment_provider: The payment provider for the payment :param frequency: Whether monthly or one-time payment/donation :param stripe_redirect_url: Where to redirect a stripe payment after success :param payment_type: Whether it's a donation or payment Returns a dict with: - message: Any error messages that apply - status: The status of the payment for the database - payment_id: The ID of the payment """ customer = None if payment_provider == PROVIDERS.PAYPAL: response = process_paypal_payment(cd_donation_form) elif payment_provider == PROVIDERS.CREDIT_CARD: stripe_token = request.POST.get("stripeToken") stripe_args = {"metadata": {"type": payment_type}} if frequency == FREQUENCIES.ONCE: stripe_args["card"] = stripe_token elif frequency == FREQUENCIES.MONTHLY: customer = create_stripe_customer(stripe_token, cd_user_form["email"]) stripe_args["customer"] = customer.id stripe_args["metadata"].update({"recurring": True}) else: raise NotImplementedError("Unknown frequency value: %s" % frequency) if cd_donation_form["reference"]: stripe_args["metadata"].update( {"reference": cd_donation_form["reference"]}) # Calculate the amount in cents amount = int(float(cd_donation_form["amount"]) * 100) response = process_stripe_payment(amount, cd_user_form["email"], stripe_args, stripe_redirect_url) else: raise PaymentFailureException("Unknown/unhandled payment provider.") return response, customer
def create_stripe_customer(source, email): """Create a stripe customer so that we can charge this person more than once """ stripe.api_key = settings.STRIPE_SECRET_KEY try: return stripe.Customer.create(source=source, email=email) except (stripe.error.CardError, stripe.error.InvalidRequestError) as e: logger.warn("Stripe was unable to create the customer: %s" % e) message = ('Oops, we had an error with your donation: ' '<strong>%s</strong>' % e.json_body['error']['message']) raise PaymentFailureException(message)
def process_stripe_payment( amount: int, email: str, kwargs: Dict[str, Union[str, bool, Dict[str, str]]], stripe_redirect_url: str, ) -> Dict[str, Union[str, int]]: """Process a stripe payment. :param amount: The amount, in pennies, that you wish to charge :param email: The email address of the person being charged :param kwargs: Keyword arguments to pass to Stripe's `create` method. Some functioning options for this dict are: {'card': stripe_token} And: {'customer': customer.id} Where stripe_token is a token returned by Stripe's client-side JS library, and customer is an object returned by stripe's customer creation server- side library. :param stripe_redirect_url: Where to send the user after a successful transaction :return: response object with information about whether the transaction succeeded. """ stripe.api_key = settings.STRIPE_SECRET_KEY # Create the charge on Stripe's servers try: charge = stripe.Charge.create( amount=amount, currency="usd", description=email, **kwargs ) response = { "status": Donation.AWAITING_PAYMENT, "payment_id": charge.id, "redirect": stripe_redirect_url, } except (stripe.error.CardError, stripe.error.InvalidRequestError) as e: logger.info("Stripe was unable to process the payment: %s" % e) message = ( "Oops, we had an error with your donation: " "<strong>%s</strong>" % e.json_body["error"]["message"] ) raise PaymentFailureException(message) return response
def process_stripe_payment(amount, email, kwargs): """ Process a stripe payment. :param amount: The amount, in pennies, that you wish to charge :param email: The email address of the person being charged :param kwargs: Keyword arguments to pass to Stripe's `create` method. Some functioning options for this dict are: {'card': stripe_token} And: {'customer': customer.id} Where stripe_token is a token returned by Stripe's client-side JS library, and customer is an object returned by stripe's customer creation server- side library. :return: response object with information about whether the transaction succeeded. """ stripe.api_key = settings.STRIPE_SECRET_KEY # Create the charge on Stripe's servers try: charge = stripe.Charge.create(amount=amount, currency="usd", description=email, **kwargs) response = { 'status': Donation.AWAITING_PAYMENT, 'payment_id': charge.id, 'redirect': reverse('stripe_complete'), } except (stripe.error.CardError, stripe.error.InvalidRequestError) as e: logger.warn("Stripe was unable to process the payment: %s" % e) message = ('Oops, we had an error with your donation: ' '<strong>%s</strong>' % e.json_body['error']['message']) raise PaymentFailureException(message) return response
def get_paypal_access_token() -> str: """Get a token for the PayPal API. Query the PayPal API to get an access token. This could be improved by caching the token and detecting when it is expired. """ r = requests.post( "%s/v1/oauth2/token" % settings.PAYPAL_ENDPOINT, headers={ "Accept": "application/json", "Accept-Language": "en_US" }, auth=(settings.PAYPAL_CLIENT_ID, settings.PAYPAL_SECRET_KEY), data={"grant_type": "client_credentials"}, ) if r.status_code == HTTP_200_OK: logger.info("Got paypal token successfully.") else: logger.warning("Problem getting paypal token status_code was: %s, " "with content: %s" % (r.status_code, r.text)) raise PaymentFailureException( "Oops, sorry. PayPal had an error. Please try again.") return json.loads(r.content).get("access_token")
def process_paypal_payment( cd_donation_form: CleanedDonationFormType, ) -> Dict[str, str]: # https://developer.paypal.com/webapps/developer/docs/integration/web/accept-paypal-payment/ access_token = get_paypal_access_token() if not access_token: raise PaymentFailureException("NO_ACCESS_TOKEN") return_url = "https://www.courtlistener.com%s" % reverse("paypal_callback") cancel_url = "https://www.courtlistener.com%s" % reverse("paypal_cancel") data = { "intent": "sale", "redirect_urls": {"return_url": return_url, "cancel_url": cancel_url}, "payer": {"payment_method": "paypal"}, "transactions": [ { "amount": { "total": cd_donation_form["amount"], "currency": "USD", }, "description": "Donation to Free Law Project", } ], } r = requests.post( "%s/v1/payments/payment" % settings.PAYPAL_ENDPOINT, headers={ "Content-Type": "application/json", "Authorization": "Bearer %s" % access_token, }, data=json.dumps(data), timeout=30, ) if r.status_code == HTTP_201_CREATED: r_content_as_dict = json.loads(r.content) # Get the redirect value from the 'links' attribute. Links look like: # [{u'href': u'https://api.sandbox.paypal.com/v1/payments/payment/PAY-8BC403022U6413151KIQPC2I', # u'method': u'GET', # u'rel': u'self'}, # {u'href': u'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-6VV58324J9479725S', # u'method': u'REDIRECT', # u'rel': u'approval_url'}, # {u'href': u'https://api.sandbox.paypal.com/v1/payments/payment/PAY-8BC403022U6413151KIQPC2I/execute', # u'method': u'POST', # u'rel': u'execute'} # ] redirect = [ link for link in r_content_as_dict["links"] if link["rel"].lower() == "approval_url" ][0]["href"] parsed_redirect = urlparse(redirect) token = parse_qs(parsed_redirect.query)["token"][0] response = { "status": Donation.AWAITING_PAYMENT, "payment_id": r_content_as_dict.get("id"), "transaction_id": token, "redirect": redirect, } logger.info("Created payment in paypal with response: %s", response) return response else: raise PaymentFailureException("UNABLE_TO_MAKE_PAYMENT")