def _execute_subscription_action(self, subscription_id: str, action_name: str, reason: str = None) -> PaypalApiResponse: """Executes a generic and simple subscription action call to the Paypal API Arguments: subscription_id {str} -- The subscription id action_name {str} -- API URL action name Keyword Arguments: reason {str} -- A comment or reason if needed (default: {None}) Returns: PaypalApiResponse -- [description] """ body = json.dumps({'reason': reason}) if reason else None url = parse_url(self._base_url, subscription_id, action_name) api_response = self._session.post(url, body) if api_response.status_code // 100 != 2: return PaypalApiResponse.error(api_response) return PaypalApiResponse.success(api_response)
def show_tracking_info( self, transaction_id: str, tracking_number: int, response_type: ResponseType = ResponseType.MINIMAL ) -> PaypalApiResponse[Tracker]: """Gets the tracking info for a given tracker key Arguments: transaction_id {str} -- The id of the transaction tracking_number {int} -- The tracking number Keyword Arguments: response_type {ResponseType} -- [description] (default: {ResponseType.MINIMAL}) Returns: PaypalApiResponse[Tracker] -- Response container with a Tracker instance including the required information """ headers = {'Prefer': response_type.as_header_value()} url = parse_url(self._base_url, f'{transaction_id}-{tracking_number}') api_response = self._session.get(url, None, headers=headers) if api_response.status_code != 200: return PaypalApiResponse(True, api_response) return PaypalApiResponse( False, api_response, Tracker.serialize_from_json(api_response.json(), response_type))
def add_trackers( self, trackers: List[Tracker], response_type: ResponseType = ResponseType.MINIMAL ) -> PaypalApiResponse[Tracker]: """Adds trackers to a transaction Arguments: trackers {List[Tracker]} -- [description] Returns: PaypalApiResponse[Tracker] -- [description] """ body = [] url = parse_url(self._base_url, 'trackers-batch') headers = {'Prefer': response_type.as_header_value()} for t in trackers: b = t.to_dict() b['shipment_date'] = b.pop('_shipment_date', None) b['last_updated_time'] = b.pop('_last_updated_time', None) self._clean_dictionary(b, _TRACKER_PROPERTIES) body.append(b) api_response = self._session.post(url, json.dumps(body), headers=headers) if api_response.status_code != 200: return PaypalApiResponse(True, api_response) return PaypalApiResponse( False, api_response, Tracker.serialize_from_json(api_response.json(), response_type))
def update_subscription( self, subscription_id: str, update_request: List[PatchUpdateRequest]) -> PaypalApiResponse: """Patch request to update a subscription. See the docs for details Arguments: subscription_id {str} -- id of the subscription to be updated. update_request {PatchUpdateRequest} -- request object with update info. Returns: PaypalApiResponse -- Response obj with operation status """ url = parse_url(self._base_url, subscription_id) body = json.dumps([{ 'op': x.operation, 'value': x.value, 'path': x.path } for x in update_request]) api_response = self._session.patch(url, body) if api_response.status_code // 100 != 2: return PaypalApiResponse.error(api_response) return PaypalApiResponse.success(api_response)
def update_quantity_in_subscription( self, subscription_id: str, update: SubscriptionQuantityUpdateRequest ) -> PaypalApiResponse[Subscription]: """Calls the paypal API to updates the quantity of the product or service in a subscription. this method can also be used to to switch the plan and update the shipping_amount, shipping_address values for the subscription. This type of update requires the buyer's consent. Arguments: subscription_id {str} -- the subscription id update {SubscriptionQuantityUpdateRequest} -- update request info. Returns: PaypalApiResponse[Subscription] -- Response status with subscription details """ body = json.dumps(update.to_dict()) url = parse_url(self._base_url, subscription_id, 'revise') api_response = self._session.post(url, body) if api_response.status_code // 100 != 2: return PaypalApiResponse.error(api_response) return PaypalApiResponse.success( api_response, Subscription.serialize_from_json(api_response.json()))
def accept_claim(self, dispute_id: str, refund_amount: Money = None, **kwargs) -> PaypalApiBulkResponse[ActionLink]: """Calls the paypal API to accept a claim & close the dispute in favor of the customer Arguments: dispute_id {str} -- [description] Keyword Arguments: note {str} -- Notes & comments accept_claim_reason {str} -- Reason to accept the claim (default REASON_NOT_SET) invoice_id {str} -- The merchant-provided ID of the invoice for the refund. return_shipping_address {PaypalPortableAddress} -- The return address for the item. refund_amount {Money} -- To accept a customer's claim, the amount that the merchant agrees to refund the customer. Returns: PaypalApiBulkResponse[ActionLink] -- action links related to the dispute """ if refund_amount: kwargs['refund_amount'] = refund_amount.to_dict() self._remove_null_entries(kwargs['refund_amount']) if 'return_shipping_address' in kwargs.keys() and isinstance( kwargs['return_shipping_address'], PaypalPortableAddress): kwargs['return_shipping_address'] = kwargs[ 'return_shipping_address'].to_dict() self._remove_null_entries(kwargs['return_shipping_address']) return self._execute_basic_dispute_action( parse_url(self._base_url, dispute_id, 'accept-claim'), kwargs)
def resend_event_notification( self, event_id: str, webhooks_ids: List[str] = []) -> PaypalApiResponse[WebhookEvent]: """Calls the paypal API to resend notification events Arguments: event_id {str} -- The webhook event id Keyword Arguments: webhooks_ids {List[str]} -- webhooks notification to resend (all pending if empty) (default: {[]}) Returns: PaypalApiResponse[WebhookEvent] -- A response with the webhooks events. """ body = {'webhook_ids': webhooks_ids or []} api_response = self._session.post(parse_url(self._base_url, event_id), json.dumps(body)) if api_response.status_code // 100 != 2: return PaypalApiResponse.error(api_response) return PaypalApiResponse.success( api_response, WebhookEvent.serialize_from_json(api_response.json()))
def void_payment(self, authorization_id: str, auth_assertion_token: str = None) -> PaypalApiResponse: """Calls the paypal API to void an authorized payment, by ID. See the api docs for a full set of rules. Arguments: authorization_id {str} -- The authorization id Keyword Arguments: auth_assertion_token {str} -- PayPal-Auth-Assertion see: https://developer.paypal.com/docs/api/payments/v2/docs/api/reference/api-requests/#paypal-auth-assertion (default: {None}) Returns: PaypalApiResponse -- An api response with the authorization details. """ api_response = None url = parse_url(self._base_url, authorization_id, 'void') if auth_assertion_token: api_response = self._session.post( url, None, headers={'PayPal-Auth-Assertion': auth_assertion_token}) else: api_response = self._session.post(url, None) if api_response.status_code != 204: return PaypalApiResponse(True, api_response) return PaypalApiResponse(False, api_response)
def test_parse_url(self): """url parsing function test """ expected = 'https://api.sandbox.paypal.com/v2/billing/subscriptions' actual = parse_url('https://api.sandbox.paypal.com/v2', 'billing', '/subscriptions') self.assertEqual(expected, actual)
def appeal_dispute( self, dispute_id: str, evidence: DisputeEvidence, files: MIMEApplication, return_addr: PaypalPortableAddress = None ) -> PaypalApiBulkResponse[ActionLink]: """Appeals a dispute Arguments: dispute_id {str} -- Dispute identifier evidence {DisputeEvidence} -- Appeal evidence files {List[bytes]} -- files to be uploaded (current limit 10MB, 5 max per file) Returns: PaypalApiBulkResponse[ActionLink] -- [description] """ url = parse_url(self._base_url, dispute_id, 'appeal') json_part = dict() json_part['evidences'] = evidence.to_dict() if return_addr: json_part['return_address'] = return_addr.to_dict() self._remove_null_entries(json_part['return_address']) return self._execute_evidence_multipart_request(url, json_part, files)
def provide_evidence( self, dispute_id: str, evidence: DisputeEvidence, files: MIMEApplication, return_addr: PaypalPortableAddress = None ) -> PaypalApiBulkResponse[ActionLink]: """Calls the API to provide evidence Arguments: dispute_id {str} -- The dispute identifier evidence {DisputeEvidence} -- The evidence to be supported files {MIMEApplication} -- Files regarding the evidence as MIMEApplication Keyword Arguments: return_addr {PaypalPortableAddress} -- Portable return addr if needed (default: {None}) Returns: PaypalApiBulkResponse[ActionLink] -- action links related to the dispute """ url = parse_url(self._base_url, dispute_id, 'provide-evidence') json_part = dict() json_part['evidences'] = evidence.to_dict() if return_addr: json_part['return_address'] = return_addr.to_dict() self._remove_null_entries(json_part['return_address']) return self._execute_evidence_multipart_request(url, json_part, files)
def record_invoice_refund( self, invoice_id: str, refund_detail: RefundDetail) -> PaypalApiResponse: """Calls the API to record a refund for the invoice. If all payments are refunded, the invoice is marked as REFUNDED. Otherwise, the invoice is marked as PARTIALLY REFUNDED. Arguments: invoice_id {str} -- id of the invoice payment_detail {RefundDetail} -- payment details Returns: PaypalApiResponse -- Api operation response status containing the response """ url = parse_url(self._base_url, 'invoices', invoice_id, 'refunds') body = json.dumps( {k: v for k, v in refund_detail.to_dict().items() if v != None}) response = self._session.post(url, body) if response.status_code != 204: return PaypalApiResponse.error(response) return PaypalApiResponse.success(response)
def generate_qr_code(self, invoice_id: str, width: int = 500, height: int = 500, action: str = 'pay') -> PaypalApiResponse: """ Calls the paypal API to generate a QR code for an invoice. The QR code is a PNG image in Base64-encoded format that corresponds to the invoice ID. You can generate a QR code for an invoice and add it to a paper or PDF invoice. When customers use their mobile devices to scan the QR code, they are redirected to the PayPal mobile payment flow where they can view the invoice and pay online with PayPal or a credit card. Before you get a QR code, you must create an invoice and send an invoice to move the invoice from a draft to payable state. Do not include an email address if you do not want the invoice emailed. Arguments: invoice_id {str} -- existing invoice id Keyword Arguments: width {int} -- The width, in pixels, of the QR code image. Value is from 150 to 500 (default: {500}) height {int} -- The height, in pixels, of the QR code image. Value is from 150 to 500 (default: {500}) action {str} -- The type of URL for which to generate a QR code. Valid values are 'pay' and 'details'. (default: {'pay'}) Returns: PaypalApiResponse -- Api operation response status containing the response """ url = parse_url(self._base_url, 'invoices', invoice_id, 'generate-qr-code') body = json.dumps({'width': width, 'height': height, 'action': action}) response = self._session.post(url, body) if response.status_code != 200: return PaypalApiResponse.error(response) return PaypalApiResponse.success(response)
def list_subscription_transactions( self, subscription_id: str, start_time: datetime, end_time: datetime) -> PaypalPage[SubscriptionTransaction]: """Calls the API to lists transactions for a subscription. Arguments: subscription_id {str} -- The subscription id start_time {datetime} -- transaction start time end_time {datetime} -- transaction end time Returns: PaypalPage[SubscriptionTransaction] -- Paged transaction info """ fmt = '%Y-%m-%dT%H:%M:%S' url = parse_url(self._base_url, subscription_id, 'transactions') params = {end_time.strftime(fmt), start_time.strftime(fmt)} api_response = self._session.get(url, params) if api_response.status_code // 100 != 2: return PaypalPage.error(api_response) return PaypalPage.full_parse_success(api_response, SubscriptionTransaction, 'transaction')
def send_invoice( self, invoice_id: str, subject: str, note: str, send_to_invoicer: bool, send_to_recipient: bool, additional_recipients: List[str] = [], paypal_request_id: str = None) -> PaypalApiResponse[Invoice]: """Calls the paypal API to send or schedule an invoice, by ID, to be sent to a customer. The action depends on the invoice issue date: If the invoice issue date is current or in the past, sends the invoice immediately. If the invoice issue date is in the future, schedules the invoice to be sent on that date. To suppress the merchant's email notification, set the send_to_invoicer body parameter to false. To send the invoice through a share link and not through PayPal, set the send_to_recipient parameter to false in the notification object. The send_to_recipient parameter does not apply to a future issue date because the invoice is scheduled to be sent through PayPal on that date. Arguments: invoice_id {str} -- invoice id subject {str} -- The subject of the email that is sent as a notification to the recipient. note {str} -- A note to the payer. send_to_invoicer {bool} -- Indicates whether to send a copy of the email to the merchant. send_to_recipient {bool} -- Indicates whether to send a copy of the email to the recipient. Keyword Arguments: paypal_request_id {str} -- Paypal request id for idempotence. (default: {None}) additional_recipients {List[str]} -- An array of one or more CC: emails to which notifications are sent. (default: {[]}) Returns: PaypalApiResponse -- Api operation response status with parsed objects within """ response = None url = parse_url(self._base_url, 'invoices', invoice_id, 'send') body = json.dumps({ 'subject': subject, 'note': note, 'send_to_invoicer': send_to_invoicer, 'send_to_recipient': send_to_recipient }) if additional_recipients: body['additional_recipients'] = [{ 'email_address': x } for x in additional_recipients] if paypal_request_id: response = self._session.post( url, body, headers={'PayPal-Request-Id': paypal_request_id}) else: response = self._session.post(url, body) if response.status_code // 100 != 2: return PaypalApiResponse.error(response) if response.status_code == 200: return PaypalApiResponse.success( response, Invoice.serialize_from_json(response.json())) return PaypalApiResponse.success(response)
def capture_authorized_payment( self, authorization_id: str, invoice_id: str = None, note_to_payer: str = None, instruction: PaymentInstruction = None, amount: Money = None, final_capture: bool = False, request_id: str = None, response_type: ResponseType = ResponseType.MINIMAL ) -> PaypalApiResponse[Capture]: """Calls the paypal API to capture an authorized payment, by ID. Arguments: authorization_id {str} -- authorization id invoice_id {str} -- associated invoice note_to_payer {str} -- informational notes about the settlement. Appears in the payer's transaction history and received emails. Keyword Arguments: instruction {PaymentInstruction} -- Any additional payment instructions for PayPal for Partner customers (default: {None}) amount {Money} -- The amount to capture. If none the full amount will be captured (default: {None}). final_capture {bool} -- dictates whether you can make additional captures against the authorized payment (default: {False}). request_id {str} -- Paypal request id for idempotence (default: {None}) response_type {ResponseType} -- desired response type (default: {ResponseType.MINIMAL}) Returns: PaypalApiResponse[Authorization] -- An api response with the authorization details """ body = dict() url = parse_url(self._base_url, authorization_id, 'capture') headers = {'Prefer': response_type.as_header_value()} if request_id: headers['PayPal-Request-Id'] = request_id if invoice_id: body['invoice_id'] = invoice_id if note_to_payer: body['note_to_payer'] = note_to_payer if final_capture: body['final_capture'] = final_capture if instruction: body['instruction'] = instruction if amount: body['amount'] = amount.to_dict() api_response = self._session.post(url, json.dumps(body), headers=headers) if api_response.status_code != 201: return PaypalApiResponse(True, api_response) return PaypalApiResponse( False, api_response, Capture.serialize_from_json(api_response.json()))
def refund_capture( self, capture_id: str, invoice_id: str, note_to_payer: str, amount: Money = None, request_id: str = None, auth_assertion_token: str = None, response_type: ResponseType = ResponseType.MINIMAL ) -> PaypalApiResponse[Refund]: """Calls the api to refund a capture Arguments: capture_id {str} -- capture identifier invoice_id {str} -- invoice related capture note_to_payer {str} -- notes to the customer Keyword Arguments: amount {Money} -- amount to be refunded if None then 'captured amount - previous refunds' (default: {None}) request_id {str} -- request id for idempotence (default: {None}) auth_assertion_token {str} -- auth assertion token. See paypal header docs (default: {None}) response_type {ResponseType} -- response type. See paypal header docs (default: {ResponseType.MINIMAL}) Returns: PaypalApiResponse[Refund] -- api response with refund details """ body = dict() url = parse_url(self._base_url, capture_id, 'refund') headers = {'Prefer': response_type.as_header_value()} if request_id: headers['PayPal-Request-Id'] = request_id if auth_assertion_token: headers['PayPal-Auth-Assertion'] = auth_assertion_token if invoice_id: body['invoice_id'] = invoice_id if note_to_payer: body['note_to_payer'] = note_to_payer if amount: body['amount'] = amount.to_dict() api_response = self._session.post(url, json.dumps(body), headers=headers) if api_response.status_code != 201: return PaypalApiResponse(True, api_response) return PaypalApiResponse( False, api_response, Refund.serialize_from_json(api_response.json()))
def show_webhook_details(self, webhook_id: str) -> PaypalApiResponse[Webhook]: """Calls the paypal API to show a webhook details Arguments: webhook_id {str} -- Webhook id Returns: PaypalApiResponse[Webhook] -- Response status with webhook object. """ return self._process_simple_response( self._session.get(parse_url(self._base_url, webhook_id)))
def for_session(cls: T, session: PayPalSession) -> I: """Creates a client from a given paypal session Arguments: cls {T} -- class reference session {PayPalSession} -- the paypal session Returns: T -- an instance of Dispute client with the right configuration by session mode """ base_url = _LIVE_RESOURCE_BASE_URL if session.session_mode.is_live() else _SANDBOX_RESOURCE_BASE_URL return cls(parse_url(base_url, 'payouts-item'), session)
def capture_payment_for_order( self, order_id: str, payment_source: PaymentSource = None, request_id: str = None, client_metadata_id: str = None, auth_assertion: str = None, response_type: ResponseType = ResponseType.MINIMAL ) -> PaypalApiResponse[Order]: """Calls the API to Capture a payment for an order. To successfully authorize payment for an order, the buyer must first approve the order or a valid payment_source must be provided in the request. Arguments: order_id {str} -- The order id Keyword Arguments: payment_source {PaymentSource} -- Source of payment for the order if the user wasn't redirected in the order creation to approve the payment. (default: {None}) request_id {str} -- Request id for idempotence (default: {None}) client_metadata_id {str} -- Verifies that the payment originates from a valid, user-consented device and application. Must be included in order to be eligible for PayPal Seller Protection. (default: {None}) auth_assertion {str} -- A JWT assertion that identifies the merchant (default: {None}) response_type {ResponseType} -- (default: {ResponseType.MINIMAL}) Returns: PaypalApiResponse[Order] -- API operation response with the order if successful """ url = parse_url(self._base_url, order_id, 'capture') headers = {'Prefer': response_type.as_header_value()} if request_id: headers['PayPal-Request-Id'] = request_id if auth_assertion: headers['PayPal-Auth-Assertion'] = auth_assertion if client_metadata_id: headers['PayPal-Client-Metadata-Id'] = client_metadata_id if payment_source: api_response = self._session.post(url, json.dumps( payment_source.to_dict()), headers=headers) else: api_response = self._session.post(url, None) if api_response.status_code // 100 != 2: return PaypalApiResponse(True, api_response) return PaypalApiResponse( False, api_response, Order.serialize_from_json(api_response.json(), response_type))
def delete_template(self, template_id: str) -> PaypalApiResponse: """Deletes a template by id Arguments: template_id {str} -- The id of the template to be deleted Returns: PaypalApiResponse -- API Response. """ response = self._session.delete(parse_url(self._base_url, template_id)) return PaypalApiResponse.success( response ) if response.status_code == 204 else PaypalApiResponse.error(response)
def list_referenced_batch_payout_items( self, payouts_batch_id: str ) -> PaypalApiResponse[ReferencedPayoutResponse]: """Calls the API to list the payout items in a referenced batch payout. Each item in the list includes payout item details. Arguments: payouts_batch_id {str} -- The batch id Returns: PaypalApiResponse[ReferencedPayoutResponse] -- Listed referenced payouts with the directive """ return self._process_referenced_list_response( self._session.get(parse_url(self._base_url, payouts_batch_id)))
def send_message_to_third_party( self, dispute_id: str, message: str) -> PaypalApiBulkResponse[ActionLink]: """Calls the api to send a message about a dispute, by ID, to the other party in the dispute Arguments: dispute_id {str} -- The dispute id message {str} -- Message to be sent Returns: PaypalApiBulkResponse[ActionLink] -- action links related to the dispute """ url = parse_url(self._base_url, dispute_id, 'send-message') return self._execute_basic_dispute_action(url, {'message': message})
def escalate_to_claim(self, dispute_id: str, note: str) -> PaypalApiBulkResponse[ActionLink]: """Calls the API to escalate the dispute, by ID, to a PayPal claim. Arguments: dispute_id {str} -- The dispute identifier note {str} -- Customer notes about the escalation Returns: PaypalApiBulkResponse[ActionLink] -- action links related to the dispute """ return self._execute_basic_dispute_action( parse_url(self._base_url, dispute_id, 'deny-offer'), {'note': note})
def deny_offer(self, dispute_id: str, note: str) -> PaypalApiBulkResponse[ActionLink]: """Calls the API to deny an offer that the merchant proposes for a dispute. Arguments: dispute_id {str} -- The dispute identifier note {str} -- Customer notes about the denial of offer Returns: PaypalApiBulkResponse[ActionLink] -- action links related to the dispute """ return self._execute_basic_dispute_action( parse_url(self._base_url, dispute_id, 'deny-offer'), {'note': note})
def accept_offer(self, dispute_id: str, note: str) -> PaypalApiBulkResponse[ActionLink]: """Performs an API call to execute a customer acceptance of an offer from the merchant to resolve a dispute. Arguments: dispute_id {str} -- The dispute identifier note {str} -- The customer notes about accepting of offer. Returns: PaypalApiBulkResponse[ActionLink] -- action links related to the dispute """ return self._execute_basic_dispute_action( parse_url(self._base_url, dispute_id, 'accept-offer'), {'note': note})
def update_product(self, product_id: str, updates: List[ProductUpdateRequest]) -> PaypalApiResponse: """Updates a product Arguments: product_id {str} -- [description] updates {List[ProductUpdateRequest]} -- [description] Returns: PaypalApiResponse -- [description] """ url = parse_url(self._base_url, product_id) body = [ {'op': x.operation, 'path': x.path, 'value': x.value} for x in updates ] api_response = self._session.patch(url, json.dumps(body)) return PaypalApiResponse(api_response.status_code != 204, api_response)
def acknowledge_returned_item( self, dispute_id: str, note: str) -> PaypalApiBulkResponse[ActionLink]: """Performs an API call to acknowledge that the customer returned an item for a dispute. Arguments: dispute_id {str} -- The dispute identifier note {str} -- Merchant provided notes. Returns: PaypalApiBulkResponse[ActionLink] -- action links related to the dispute """ return self._execute_basic_dispute_action( parse_url(self._base_url, dispute_id, 'acknowledge-return-item'), {'note': note})
def show_payout_item_details(self, payout_item_id: str) -> PaypalApiResponse[PayoutItem]: """Calls the paypal API to show a payout item details Arguments: payout_item_id {str} -- Desired payout item id Returns: PaypalApiResponse[PayoutItem] -- Response with the item data if successful """ api_response = self._session.get(parse_url(self._base_url, payout_item_id)) if api_response.status_code // 100 != 2: return PaypalApiResponse.error(api_response) return PaypalApiResponse.success(api_response, PayoutItem.serialize_from_json(api_response.json()))
def show_refund_details(self, refund_id: str) -> PaypalApiResponse[Refund]: """Calls the paypal API to show details for an authorized payment, by ID. Arguments: refund_id {str} -- The refund id Returns: PaypalApiResponse[Refund] -- An api response with the refund details """ api_response = self._session.get(parse_url(self._base_url, refund_id)) if api_response.status_code != 200: return PaypalApiResponse(True, api_response) return PaypalApiResponse(False, api_response, Refund.serialize_from_json(api_response.json()))