fix: handle api changes from callbacks
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index dcf302d..41a135f 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -83,8 +83,17 @@
 
 		elif self.payment_channel == "Phone":
 			controller = get_payment_gateway_controller(self.payment_gateway)
-			print(vars(self))
-			controller.request_for_payment(**vars(self))
+			payment_record = dict(
+				reference_doctype=self.reference_doctype,
+				reference_docname=self.reference_name,
+				grand_total=self.grand_total,
+				sender=self.email_to,
+				payment_request_name=self.name,
+				currency=self.currency,
+				payment_gateway=self.payment_gateway
+			)
+			controller.validate_transaction_currency(self.currency)
+			controller.request_for_payment(**payment_record)
 
 	def on_cancel(self):
 		self.check_if_payment_entry_exists()
@@ -354,7 +363,6 @@
 def get_amount(ref_doc, payment_account=None):
 	"""get amount based on doctype"""
 	dt = ref_doc.doctype
-	print(dt)
 	if dt in ["Sales Order", "Purchase Order"]:
 		grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid)
 
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 51ac7cf..f6acd72 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -794,7 +794,7 @@
 
 	return acc
 
-def create_payment_gateway_account(gateway):
+def create_payment_gateway_account(gateway, payment_channel="Email"):
 	from erpnext.setup.setup_wizard.operations.company_setup import create_bank_account
 
 	company = frappe.db.get_value("Global Defaults", None, "default_company")
@@ -829,7 +829,8 @@
 			"is_default": 1,
 			"payment_gateway": gateway,
 			"payment_account": bank_account.name,
-			"currency": bank_account.account_currency
+			"currency": bank_account.account_currency,
+			"payment_channel": payment_channel
 		}).insert(ignore_permissions=True)
 
 	except frappe.DuplicateEntryError:
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py
index 9252f5d..d79cdaa 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py
@@ -1,4 +1,6 @@
+import base64
 import requests
+from requests.auth import HTTPBasicAuth
 import datetime
 
 class MpesaConnector():
@@ -7,6 +9,110 @@
 		self.env = env
 		self.app_key = app_key
 		self.app_secret = app_secret
-		self.sandbox_url = sandbox_url
-		self.live_url = live_url
-		self.authenticate()
\ No newline at end of file
+		if env == "sandbox":
+			self.base_url = sandbox_url
+		else:
+			self.base_url = live_url
+		self.authenticate()
+
+	def authenticate(self):
+		"""
+		To make Mpesa API calls, you will need to authenticate your app. This method is used to fetch the access token
+		required by Mpesa. Mpesa supports client_credentials grant type. To authorize your API calls to Mpesa,
+		you will need a Basic Auth over HTTPS authorization token. The Basic Auth string is a base64 encoded string
+		of your app's client key and client secret.
+
+		Returns:
+			access_token (str): This token is to be used with the Bearer header for further API calls to Mpesa.
+		"""
+		authenticate_uri = "/oauth/v1/generate?grant_type=client_credentials"
+		authenticate_url = "{0}{1}".format(self.base_url, authenticate_uri)
+		r = requests.get(
+			authenticate_url,
+			auth=HTTPBasicAuth(self.app_key, self.app_secret)
+		)
+		self.authentication_token = r.json()['access_token']
+		return r.json()['access_token']
+
+	def get_balance(self, initiator=None, security_credential=None, party_a=None, identifier_type=None,
+					remarks=None, queue_timeout_url=None,result_url=None):
+		"""
+		This method uses Mpesa's Account Balance API to to enquire the balance on a M-Pesa BuyGoods (Till Number).
+		Args:
+			initiator (str): Username used to authenticate the transaction.
+			security_credential (str): Generate from developer portal.
+			command_id (str): AccountBalance.
+			party_a (int): Till number being queried.
+			identifier_type (int): Type of organization receiving the transaction. (MSISDN/Till Number/Organization short code)
+			remarks (str): Comments that are sent along with the transaction(maximum 100 characters).
+			queue_timeout_url (str): The url that handles information of timed out transactions.
+			result_url (str): The url that receives results from M-Pesa api call.
+
+		Returns:
+			OriginatorConverstionID (str): The unique request ID for tracking a transaction.
+			ConversationID (str): The unique request ID returned by mpesa for each request made
+			ResponseDescription (str): Response Description message
+		"""
+
+		payload = {
+			"Initiator": initiator,
+			"SecurityCredential": security_credential,
+			"CommandID": "AccountBalance",
+			"PartyA": party_a,
+			"IdentifierType": identifier_type,
+			"Remarks": remarks,
+			"QueueTimeOutURL": queue_timeout_url,
+			"ResultURL": result_url
+		}
+		headers = {'Authorization': 'Bearer {0}'.format(self.authentication_token), 'Content-Type': "application/json"}
+		saf_url = "{0}{1}".format(self.base_url, "/mpesa/accountbalance/v1/query")
+		r = requests.post(saf_url, headers=headers, json=payload)
+		return r.json()
+
+	def stk_push(self, business_shortcode=None, passcode=None, amount=None, callback_url=None, reference_code=None,
+				 phone_number=None, description=None):
+		"""
+		This method uses Mpesa's Express API to initiate online payment on behalf of a customer.
+		Args:
+			business_shortcode (int): The short code of the organization.
+			passcode (str): Get from developer portal
+			amount (int): The amount being transacted
+			callback_url (str): A CallBack URL is a valid secure URL that is used to receive notifications from M-Pesa API.
+			reference_code(str): Account Reference: This is an Alpha-Numeric parameter that is defined by your system as an Identifier of the transaction for CustomerPayBillOnline transaction type.
+			phone_number(int): The Mobile Number to receive the STK Pin Prompt.
+			description(str): This is any additional information/comment that can be sent along with the request from your system. MAX 13 characters
+
+		Success Response:
+			CustomerMessage(str): Messages that customers can understand.
+			CheckoutRequestID(str): This is a global unique identifier of the processed checkout transaction request.
+			ResponseDescription(str): Describes Success or failure
+			MerchantRequestID(str): This is a global unique Identifier for any submitted payment request.
+			ResponseCode(int): 0 means success all others are error codes. e.g.404.001.03
+
+		Error Reponse:
+			requestId(str): This is a unique requestID for the payment request
+			errorCode(str): This is a predefined code that indicates the reason for request failure.
+			errorMessage(str): This is a predefined code that indicates the reason for request failure.
+		"""
+
+		time = str(datetime.datetime.now()).split(".")[0].replace("-", "").replace(" ", "").replace(":", "")
+		password = "{0}{1}{2}".format(str(business_shortcode), str(passcode), time)
+		encoded = base64.b64encode(bytes(password, encoding='utf8'))
+		payload = {
+			"BusinessShortCode": business_shortcode,
+			"Password": encoded.decode("utf-8"),
+			"Timestamp": time,
+			"TransactionType": "CustomerPayBillOnline",
+			"Amount": amount,
+			"PartyA": int(phone_number),
+			"PartyB": business_shortcode,
+			"PhoneNumber": int(phone_number),
+			"CallBackURL": callback_url,
+			"AccountReference": reference_code,
+			"TransactionDesc": description
+		}
+		headers = {'Authorization': 'Bearer {0}'.format(self.authentication_token), 'Content-Type': "application/json"}
+
+		saf_url = "{0}{1}".format(self.base_url, "/mpesa/stkpush/v1/processrequest")
+		r = requests.post(saf_url, headers=headers, json=payload)
+		return r.json()
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js
index 8a1c191..48e0c0b 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js
@@ -3,6 +3,5 @@
 
 frappe.ui.form.on('Mpesa Settings', {
 	// refresh: function(frm) {
-
 	// }
 });
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
index fb48cb5..c92c1b2 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
@@ -15,21 +15,85 @@
 from frappe.integrations.utils import create_request_log, create_payment_gateway
 from frappe.utils import get_request_site_address
 from frappe.utils.password import get_decrypted_password
+from frappe.utils import get_request_site_address
 from erpnext.erpnext_integrations.utils import create_mode_of_payment
 from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_connector import MpesaConnector
 from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_custom_fields import create_custom_pos_fields
 
 class MpesaSettings(Document):
-	supported_currencies = ["KSh"]
-
-	def validate(self):
-		create_payment_gateway('Mpesa-' + self.payment_gateway_name, settings='Mpesa Settings', controller=self.payment_gateway_name)
-		create_mode_of_payment('Mpesa-' + self.payment_gateway_name)
-		call_hook_method('payment_gateway_enabled', gateway='Mpesa-' + self.payment_gateway_name)
+	supported_currencies = ["KES"]
 
 	def validate_transaction_currency(self, currency):
 		if currency not in self.supported_currencies:
 			frappe.throw(_("Please select another payment method. Mpesa does not support transactions in currency '{0}'").format(currency))
 
 	def on_update(self):
-		create_custom_pos_fields()
\ No newline at end of file
+		create_custom_pos_fields()
+		create_payment_gateway('Mpesa-' + self.payment_gateway_name, settings='Mpesa Settings', controller=self.payment_gateway_name)
+		create_mode_of_payment('Mpesa-' + self.payment_gateway_name)
+		call_hook_method('payment_gateway_enabled', gateway='Mpesa-' + self.payment_gateway_name, payment_channel="Phone")
+
+	def request_for_payment(self, **kwargs):
+		response = frappe._dict(generate_stk_push(**kwargs))
+		# check error response
+		if hasattr(response, "requestId"):
+			req_name = getattr(response, "requestId")
+			error = response
+		else:
+			# global checkout id used as request name
+			req_name = getattr(response, "CheckoutRequestID")
+			error = None
+
+		create_request_log(kwargs, "Host", "Mpesa", req_name, error)
+		if error:
+			frappe.throw(_(getattr(response, "errorMessage")), title=_("Transaction Error"))
+
+def generate_stk_push(**kwargs):
+	args = frappe._dict(kwargs)
+	try:
+		callback_url = get_request_site_address(True) + "/api/method/erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings.verify_transaction"
+
+		mpesa_settings = frappe.get_doc("Mpesa Settings", args.payment_gateway[6:])
+		env = "production" if not mpesa_settings.sandbox else "sandbox"
+
+		connector = MpesaConnector(env=env,
+			app_key=mpesa_settings.consumer_key,
+			app_secret=mpesa_settings.get_password("consumer_secret"))
+
+		response = connector.stk_push(business_shortcode=mpesa_settings.till_number,
+			passcode=mpesa_settings.get_password("online_passkey"), amount=args.grand_total,
+			callback_url=callback_url, reference_code=args.payment_request_name,
+			phone_number=args.sender, description="POS Payment")
+
+		return response
+
+	except Exception:
+		frappe.log_error(title=_("Mpesa Express Transaction Error"))
+		frappe.throw(_("Issue detected with Mpesa configuration, check the error logs for more details"), title=_("Mpesa Express Error"))
+
+@frappe.whitelist(allow_guest=True)
+def verify_transaction(**kwargs):
+	""" Verify the transaction result received via callback """
+	transaction_response = frappe._dict(kwargs["Body"]["stkCallback"])
+
+	checkout_id = getattr(transaction_response, "CheckoutRequestID")
+	request = frappe.get_doc("Integration Request", checkout_id)
+	transaction_data = frappe._dict(json.loads(request.data))
+
+	if transaction_response['ResultCode'] == 0:
+		if transaction_data.reference_doctype and transaction_data.reference_docname:
+			try:
+				frappe.get_doc(transaction_data.reference_doctype,
+					transaction_data.reference_docname).run_method("on_payment_authorized", 'Completed')
+				request.db_set('output', transaction_response)
+				request.db_set('status', 'Completed')
+			except Exception:
+				request.db_set('error', transaction_response)
+				request.db_set('status', 'Failed')
+				frappe.log_error(frappe.get_traceback())
+
+	else:
+		request.db_set('error', transaction_response)
+		request.db_set('status', 'Failed')
+
+	frappe.publish_realtime('process_phone_payment', after_commit=True, user=request.owner, message=transaction_response)
\ No newline at end of file