Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: (fix too) Migrate to Stripe IntentPayment API #20

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions payments/overrides/payment_webform.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def get_payment_gateway_url(self, doc):
"payer_email": frappe.session.user,
"payer_name": frappe.utils.get_fullname(frappe.session.user),
"order_id": doc.name,
"payment_id": doc.payment_id,
"currency": self.currency,
"redirect_to": frappe.utils.get_url(self.success_url or self.route),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,11 @@ def create_request(self, data):

self.data = frappe._dict(data)
stripe.api_key = self.get_password(fieldname="secret_key", raise_exception=False)
stripe.default_http_client = stripe.http_client.RequestsClient()

try:
self.integration_request = create_request_log(self.data, service_name="Stripe")
return self.create_charge_on_stripe()
intent = self.create_payment()
self.data.payment_id = intent.id
return intent

except Exception:
frappe.log_error(frappe.get_traceback())
Expand All @@ -213,42 +213,74 @@ def create_request(self, data):
"status": 401,
}

def create_charge_on_stripe(self):
def create_payment(self):
import stripe

try:
charge = stripe.Charge.create(
amount=cint(flt(self.data.amount) * 100),
currency=self.data.currency,
source=self.data.stripe_token_id,
description=self.data.description,
receipt_email=self.data.payer_email,
)
data = self.data
payment_id = frappe.get_value(data.reference_doctype, data.reference_docname, 'payment_id')
# Create a PaymentIntent with the order amount and currency
if payment_id == None:
intent = stripe.PaymentIntent.create(
amount=cint(flt(data.amount) * 100),
currency=data.currency,
automatic_payment_methods={
'enabled': True,
},
description=data.description,
receipt_email=data.payer_email,
)
data.payment_id = intent.id
self.integration_request = create_request_log(data, service_name="Stripe")
frappe.get_doc(data.reference_doctype, data.reference_docname).db_set('payment_id', intent.id)
self.integration_request.db_set("status", "Authorized", update_modified=False)
else:
intent = stripe.PaymentIntent.retrieve(payment_id)

if charge.captured == True:
self.integration_request.db_set("status", "Completed", update_modified=False)
self.flags.status_changed_to = "Completed"
except Exception:
frappe.log_error(frappe.get_traceback())

return intent

def validate_payment(self, data, payment_id):
import stripe

self.data = frappe._dict(data)
stripe.api_key = self.get_password(fieldname="secret_key", raise_exception=False)

try:
data = self.data
pi = stripe.PaymentIntent.retrieve(payment_id)
self.integration_request = frappe.get_last_doc("Integration Request", filters={
'integration_request_service': 'Stripe',
'reference_doctype': data.reference_doctype,
'reference_docname': data.reference_docname,
'status': 'Authorized'
})

if pi.status != "succeeded":
self.integration_request.db_set("status", "Authorized", update_modified=False)
else:
frappe.log_error(charge.failure_message, "Stripe Payment not completed")
self.flags.status_changed_to = "Completed"

except Exception:
frappe.log_error(frappe.get_traceback())

return self.finalize_request()

def finalize_request(self):
redirect_to = self.data.get("redirect_to") or None
redirect_message = self.data.get("redirect_message") or None
status = self.integration_request.status
data = self.data
flags = self.flags
redirect_to = data.get("redirect_to") or None
redirect_message = data.get("redirect_message") or None
status = flags.status_changed_to

if self.flags.status_changed_to == "Completed":
if self.data.reference_doctype and self.data.reference_docname:
if status == 'Completed':
if data.reference_doctype and data.reference_docname:
custom_redirect_to = None
try:
custom_redirect_to = frappe.get_doc(
self.data.reference_doctype, self.data.reference_docname
).run_method("on_payment_authorized", self.flags.status_changed_to)
custom_redirect_to = frappe.get_doc(data.reference_doctype, data.reference_docname).run_method("on_payment_authorized", status)
self.integration_request.db_set("status", status, update_modified=False)
except Exception:
frappe.log_error(frappe.get_traceback())

Expand Down
3 changes: 3 additions & 0 deletions payments/payment_gateways/stripe_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from frappe.integrations.utils import create_request_log



def create_stripe_subscription(gateway_controller, data):

stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller)
stripe_settings.data = frappe._dict(data)

Expand Down Expand Up @@ -35,6 +37,7 @@ def create_stripe_subscription(gateway_controller, data):


def create_subscription_on_stripe(stripe_settings):

items = []
for payment_plan in stripe_settings.payment_plans:
plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "product_price_id")
Expand Down
207 changes: 140 additions & 67 deletions payments/templates/includes/stripe_checkout.js
Original file line number Diff line number Diff line change
@@ -1,85 +1,158 @@
var stripe = Stripe("{{ publishable_key }}");

var elements = stripe.elements();

var style = {
base: {
color: '#32325d',
lineHeight: '18px',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};
frappe.ready(() => {
var stripe = Stripe("{{ publishable_key }}");

var card = elements.create('card', {
hidePostalCode: true,
style: style
});

card.mount('#card-element');
initialize();
checkStatus();
document.querySelector("#payment-form").addEventListener("submit", handleSubmit);
var emailAddress = {{frappe.form_dict}}.payer_email;

function setOutcome(result) {
async function initialize() {

if (result.token) {
$('#submit').prop('disabled', true)
$('#submit').html(__('Processing...'))
frappe.call({
method:"payments.templates.pages.stripe_checkout.make_payment",
const response = await frappe.call({
method:"payments.templates.pages.stripe_checkout.create_payment",
freeze:true,
headers: {"X-Requested-With": "XMLHttpRequest"},
headers: {
"X-Requested-With": "XMLHttpRequest",
"X-Frappe-CSRF-Token": frappe.csrf_token
},
args: {
"stripe_token_id": result.token.id,
"data": JSON.stringify({{ frappe.form_dict|json }}),
"reference_doctype": "{{ reference_doctype }}",
"reference_docname": "{{ reference_docname }}"
"reference_docname": "{{ reference_docname }}",
}
});
const clientSecret = await response.message;
const validate = await frappe.call({
method:"payments.templates.pages.stripe_checkout.make_payment",
freeze:"true",
headers: {
"X-Requested-With": "XMLHttpRequest",
"X-Frappe-CSRF-Token": frappe.csrf_token
},
args: {
"data": JSON.stringify({{ frappe.form_dict|json }}),
"reference_doctype": "{{ reference_doctype }}",
"reference_docname": "{{ reference_docname }}",
},
callback: function(r) {
if (r.message.status == "Completed") {
$('#submit').hide()
$('.success').show()
setTimeout(function() {
window.location.href = r.message.redirect_to
}, 2000);
} else {
$('#submit').hide()
$('.error').show()
setTimeout(function() {
window.location.href = r.message.redirect_to
}, 2000);
window.location.href = r.message.redirect_to;
}
}

});

} else if (result.error) {
$('.error').html(result.error.message);
$('.error').show()
}
}

card.on('change', function(event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
const appearance = {
theme: 'stripe',
};
elements = stripe.elements({ appearance, clientSecret });

const linkAuthenticationElement = elements.create("linkAuthentication", {
defaultValues:
{ email: emailAddress }
});
linkAuthenticationElement.mount("#link-authentication-element");

frappe.ready(function() {
$('#submit').off("click").on("click", function(e) {
linkAuthenticationElement.on('change', (event) => {
emailAddress = event.value.email;
});

const paymentElementOptions = {
layout: "tabs",
};

const paymentElement = elements.create("payment", paymentElementOptions);
paymentElement.mount("#payment-element");

};
async function handleSubmit(e) {
e.preventDefault();
var extraDetails = {
name: $('input[name=cardholder-name]').val(),
email: $('input[name=cardholder-email]').val()
setLoading(true);

const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: document.URL,
receipt_email: emailAddress,
},
});

// This point will only be reached if there is an immediate error when
// confirming the payment. Otherwise, your customer will be redirected to
// your `return_url`. For some payment methods like iDEAL, your customer will
// be redirected to an intermediate site first to authorize the payment, then
// redirected to the `return_url`.
if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message);
} else {
showMessage("An unexpected error occurred.");
}
setLoading(false);
}

// Fetches the payment intent status after payment submission
async function checkStatus() {
const payment_id = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);

if (!payment_id) {
return;
}
stripe.createToken(card, extraDetails).then(setOutcome);
})
});

const { paymentIntent } = await stripe.retrievePaymentIntent(payment_id);

switch (paymentIntent.status) {
case "succeeded":
frappe.msgprint({
title: __('Success'),
indicator: 'green',
message: __("Payment succeeded.")
});
break;
case "processing":
frappe.msgprint({
title: __('Processing'),
indicator: 'yellow',
message: __("Your payment is processing.")
});
break;
case "requires_payment_method":
frappe.msgprint({
title: __('Try Again'),
indicator: 'yellow',
message: __("Your payment was not successful, please try again.")
});
break;
default:
frappe.throw("Something went wrong.");
break;
}
}
function showMessage(messageText) {
const messageContainer = document.querySelector("#payment-message");

messageContainer.classList.remove("hidden");
messageContainer.textContent = messageText;

setTimeout(function () {
messageContainer.classList.add("hidden");
messageText.textContent = "";
}, 4000);
}
// Show a spinner on payment submission
function setLoading(isLoading) {
if (isLoading) {
// Disable the button and show a spinner
document.querySelector("#submit").disabled = true;
document.querySelector("#spinner").classList.remove("hidden");
document.querySelector("#button-text").classList.add("hidden");
} else {
document.querySelector("#submit").disabled = false;
document.querySelector("#spinner").classList.add("hidden");
document.querySelector("#button-text").classList.remove("hidden");
}
}
})
15 changes: 0 additions & 15 deletions payments/templates/pages/payment_success.py

This file was deleted.