电商支付-Paypal普通支付对接基于Restful-api-v1处理纠纷/退款回调通知.Net(三)

Paypal支付Api v1是基于webhook方式处理回调通知,比PayPal IPN处理方式优雅很多,webhook将以事件分类模式将各类事件明确分类处理,这样对接方式就可以很清晰处理各种通知流程,以下将详细介绍回调处理的所有逻辑

关于PayPal关于webhook通知处理逻辑文档 

webhook官方文档
Paypal纠纷文档

交易通知流程

  1. 支付成功 (事件类型:PAYMENT.SALE.COMPLETED)
  2. 支付拒绝 (事件类型:PAYMENT.SALE.DENIED)
  3. 支付pending (针对电子支票,PayPal有一个中间确认状态)(事件类型:PAYMENT.SALE.PENDING)

退款通知流程

  1. 退款成功(事件类型:PAYMENT.SALE.REFUNDED)

争议通知流程(纠纷)

  1. 创建争议(事件类型:CUSTOMER.DISPUTE.CREATED / RISK.DISPUTE.CREATED)
  2. 更新争议状态(事件类型:CUSTOMER.DISPUTE.UPDATED)
  3. 解决争议(事件类型:CUSTOMER.DISPUTE.RESOLVED)

争议生命周期阶段

要了解争议管理流程,请了解争议的生命周期阶段:

咨询阶段

在客户通过在解决中心报告问题提出争议之后,立即进行查询阶段。客户和商家尝试在20天的时间内解决争议,而不会升级到PayPal。

索赔阶段

如果客户和商家无法在20天的查询期内解决争议,则客户或商家可以将争议升级为PayPal。争议随后进入索赔阶段。

结算阶段

为了解决索赔,PayPal考虑提交的证据并以客户或商人的名义解决争议,并将争议的结果传达给银行(如果涉及)以及商人和客户。

争议管理流程

争议可以在查询阶段或索赔阶段解决。争端解决后,便进入和解阶段。

 

代码如下:

#region V1异步处理请求
        /// <summary>
        /// webhook 异步通知处理
        /// https://developer.paypal.com/docs/integration/direct/webhooks/rest-webhooks/#verify-event-notifications
        /// https://developer.paypal.com/docs/integration/direct/customer-disputes/webhooks/?mark=disputed_transactions#webhook-details
        /// </summary>
        /// <returns></returns>
        public ActionResult Notify()
        {
            var orderId = 0;
            string ip = Vayava.Common.ClientHelper.GetIp();//获取客户端请求IP
            // Get the received request's headers
            var requestHeaders = Request.Headers;
            // Get the received request's body
            var requestBody = string.Empty;
            using (var reader = new StreamReader(Request.InputStream))
            {
                requestBody = reader.ReadToEnd();
            }
            try
            {
                // 务必验证回调通知来自于PayPal,否则可能是恶意攻击
                var isValid = PaypalUtils.ValidateReceivedEvent(requestHeaders, requestBody);
                if (isValid)
                {
                    PaymentHepler.WriteDetailLog(PaypalUtils.LogName, $"pay paypal notify is start, ip: {ip}, valid success, json: {requestBody}");
                    var request = Newtonsoft.Json.JsonConvert.DeserializeObject<Event<Object>>(requestBody);
                    var resourceType = request?.ResourceType.ToUpper();
                    var eventType = request?.EventType.ToUpper();
                    if (resourceType == "DISPUTE")   // 争议流程
                    {
                        #region 纠纷状态更新
                        var resource = Newtonsoft.Json.JsonConvert.DeserializeObject<Dispute>(request.Resource.ToString());
                        Int32.TryParse(resource?.DisputedTransactions.FirstOrDefault()?.InvoiceNumber, out orderId);
                        var disputeId = resource?.DisputeId; // 纠纷ID
                        var disputeDate = Convert.ToDateTime(resource?.CreateTime); // 纠纷日期
                        var reason = resource?.Reason; // 纠纷类型
                        var disputeCurrencyCode = resource?.DisputeAmount.CurrencyCode; // 纠纷金额币种
                        var disputeAmount = Convert.ToDecimal(resource?.DisputeAmount.Value);  // 纠纷金额
                        var status = resource?.Status?.ToUpper(); // 纠纷状态
                        var disputeOutcome = resource?.DisputeOutcome?.OutcomeCode; // 纠纷处理结果
                        var remark = resource?.Messages?.LastOrDefault()?.Content; // 获取最后一个备注内容
                        var disputeLifeCycleStage = resource?.DisputeLifeCycleStage; // 争议生命周期中的阶段。
                        var disputeChannel = resource?.DisputeChannel;  // 客户提出争议的渠道。
                        var caseState = default(CaseStateEnum);
                        var caseReason = (CaseReasonEnum)Enum.Parse(typeof(CaseReasonEnum), reason);
                        var caseType = this.GetPaypalCaseType(disputeLifeCycleStage, disputeChannel);
                        if (eventType == "CUSTOMER.DISPUTE.CREATED" || eventType == "RISK.DISPUTE.CREATED")
                        {
                            // 创建争议
                            caseState = CaseStateEnum.Open;
                        }
                        else if (eventType == "CUSTOMER.DISPUTE.UPDATED")
                        {
                            switch (status)
                            {
                                case "WAITING_FOR_SELLER_RESPONSE":
                                    caseState = CaseStateEnum.WaitingForSellerResponse;
                                    break;
                                case "WAITING_FOR_BUYER_RESPONSE":
                                    caseState = CaseStateEnum.WaitingForBuyerResponse;
                                    break;
                                case "UNDER_REVIEW":
                                    caseState = CaseStateEnum.UnderReview;
                                    break;
                                case "RESOLVED":
                                    caseState = CaseStateEnum.Resolved;
                                    break;
                                case "OPEN":
                                    caseState = CaseStateEnum.Open;
                                    break;
                                default:
                                    caseState = CaseStateEnum.Other;
                                    break;
                            }
                        }
                        else if (eventType == "CUSTOMER.DISPUTE.RESOLVED")
                        {
                            // 解决争议
                            caseState = CaseStateEnum.Resolved;
                        }
                        // 
                        this.SetOrderDisputeState(orderId, disputeId, caseType, caseState, caseReason,
                                 disputeCurrencyCode, disputeAmount, disputeDate, disputeOutcome, remark);
                        #endregion
                    }
                    else if (resourceType == "REFUND") // 退款流程
                    {
                        #region 退款状态更新
                        var resource = Newtonsoft.Json.JsonConvert.DeserializeObject<DetailedRefund>(request.Resource.ToString());
                        Int32.TryParse(resource.InvoiceNumber, out orderId);
                        var state = resource?.State.ToUpper();
                        var currency = resource?.Amount.Currency;
                        var amount = Convert.ToDecimal(resource?.Amount?.Total); // 退款总金额 = 总净额 + Paypal费用
                        var receivedAmount = resource?.RefundFromReceivedAmount?.Value; // 总净额
                        var transactionFee = resource?.RefundFromTransactionFee?.Value; // PayPal费用
                        var transactionId = resource?.Id; // 退款交易ID
                        var remark = $"总净额: {receivedAmount}, PayPal费用: {transactionFee}";
                        if (eventType == "PAYMENT.SALE.REFUNDED")
                        {
                            // 退款完成
                            this.SetOrderRefundState(orderId, false, currency, amount, transactionId, remark);
                        }
                        #endregion
                    }
                    else if (resourceType == "SALE")  // 交易流程
                    {
                        #region 付款状态更新
                        var resource = Newtonsoft.Json.JsonConvert.DeserializeObject<Sale>(request.Resource.ToString());
                        Int32.TryParse(resource.InvoiceNumber, out orderId);

                        var state = resource?.State.ToUpper();
                        var currency = resource?.Amount.Currency;
                        var amount = Convert.ToDecimal(resource?.Amount?.Total);
                        var transactionId = resource?.Id;
                        var paymentMethod = PaymentMethod.Paypal;
                        var remark = $"{eventType}";
                        if (eventType == "PAYMENT.SALE.COMPLETED")
                        {
                            // 付款完成
                            this.SetOrderPaymentState(orderId, 2, currency, amount, transactionId, paymentMethod, remark);
                        }
                        else if (eventType == "PAYMENT.SALE.PENDING")
                        {
                            // 付款pending
                            this.SetOrderPaymentState(orderId, 1, currency, amount, transactionId, paymentMethod, remark);
                        }
                        else if (eventType == "PAYMENT.SALE.DENIED")
                        {
                            // 付款拒绝
                            this.SetOrderPaymentState(orderId, 0, currency, amount, transactionId, paymentMethod, remark);
                        }
                        #endregion
                    }
                }
                else
                {
                    // 验签失败
                    PaymentHepler.WriteDetailLog(PaypalUtils.LogName, $"pay paypal notify valid SignMsg is failed.", orderId);
                    return Content("1");
                }
            }
            catch (Exception ex)
            {
                PaymentHepler.WriteDetailLog(PaypalUtils.LogName, $"pay paypal notify, ex: {ex.ToString()}", orderId);

            }
            return Content("1");
        }

        /// <summary>
        /// 得到纠纷类型
        /// 客户联系PayPal, 纠纷查询阶段
        /// 客户联系PayPal,纠纷索赔阶段
        /// 客户联系其发卡行或银行要求退款。(不经过paypal)
        /// </summary>
        /// <param name="disputeLifeCycleStage"></param>
        /// <param name="disputeChannel"></param>
        /// <returns></returns>
        private CaseTypeEnum GetPaypalCaseType(string disputeLifeCycleStage, string disputeChannel)
        {
            var caseType = CaseTypeEnum.Dispute;
            if (string.IsNullOrEmpty(disputeLifeCycleStage) || string.IsNullOrEmpty(disputeChannel))
                return caseType;
            if (disputeLifeCycleStage.ToUpper() == "INQUIRY" && disputeChannel.ToUpper() == "INTERNAL")
            {
                // 客户联系贝宝(PayPal)向商家提出争议, 此时处于纠纷查询阶段
                caseType = CaseTypeEnum.Dispute;
            }
            else if (disputeLifeCycleStage.ToUpper() == "CHARGEBACK" && disputeChannel.ToUpper() == "INTERNAL")
            {
                // 客户联系贝宝(PayPal)向商家提出争议, 此时处于纠纷索赔阶段
                caseType = CaseTypeEnum.Claim;
            }
            else if (disputeLifeCycleStage.ToUpper() == "CHARGEBACK" && disputeChannel.ToUpper() == "EXTERNAL")
            {
                // 客户联系其发卡行或银行要求退款。
                caseType = CaseTypeEnum.Chargeback;
            }
            return caseType;
        }
        #endregion

电商支付(一)

电商支付(二)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值