前言
跨境电商行业中,支付环节相信大家都不陌生,对接的国外支付平台可能很多,但是PayPal作为全球最重要的支付方式之一,支付转化率也有保证,一直深受各跨境电商平台青睐,本章将继续介绍PayPal对接示例实战,
本次讲解是针对PayPal官方文档Payments支付流程介绍,基于.Net代码示例
PayPal参考文档
名词解释
- 同步回调:及时处理支付状态的处理流程
- 异步回调:异步处理支付状态的处理流程(支付状态最终以异步回传为准)
配置准备工作
登录PayPal沙盒开发者中心,



endpoint:paypal api地址
mode:沙盒/生产
connectionTimeout: 超时时间
requestRetries:重试次数
clientId:客户端ID
clientSecret:私钥(不要泄漏)
webhook.id:回调ID
配置文件配置如下
<configSections>
<section name="paypal" type="PayPal.Manager.SDKConfigHandler, PayPalCoreSDK" />
</configSections>
<paypal>
<settings>
<add name="endpoint" value="https://api.sandbox.paypal.com/" />
<add name="oauth.EndPoint" value="https://api.sandbox.paypal.com/" />
<add name="mode" value="sandbox" />
<add name="connectionTimeout" value="360000" />
<add name="requestRetries" value="1" />
<add name="clientId" value="「clientId」" />
<add name="clientSecret" value="「clientSecret」" />
<add name="webhook.id" value="「webhook.id」" />
</settings>
</paypal>
请求授权获取Token
定义PaypalUtils工具类
static PaypalUtils()
{
var config = GetConfig();
ClientId = config["clientId"];
ClientSecret = config["clientSecret"];
Endpoint = config["endpoint"];
WebhookId = config["webhook.id"];
}
// Create the configuration map that contains mode and other optional configuration details.
public static Dictionary<string, string> GetConfig()
{
var result = ConfigManager.Instance.GetProperties();
result["clientId"] = result["clientId"];
result["clientSecret"] = result["clientSecret"];
result["webhook.id"] = result["webhook.id"];
return result;
}
// Create accessToken
public static string GetAccessToken()
{
// ###AccessToken
// Retrieve the access token from
// OAuthTokenCredential by passing in
// ClientID and ClientSecret
// It is not mandatory to generate Access Token on a per call basis.
// Typically the access token can be generated once and
// reused within the expiry window
var accessToken = string.Empty;
try
{
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls | SecurityProtocolType.Ssl3;
ServicePointManager.DefaultConnectionLimit = 9999;
accessToken = new OAuthTokenCredential(ClientId, ClientSecret, GetConfig()).GetAccessToken();
}
catch (Exception ex)
{
PaymentHepler.WriteErrorLog(PaypalUtils.LogName, $"GetAccessToken error, {ex.ToString()}", ex);
}
return accessToken;
}
/// <summary>
/// 获取授权头信息
/// </summary>
/// <returns></returns>
public static Dictionary<string, string> GetHeaders(string token)
{
var headers = new Dictionary<string, string>()
{
{ "authorization", $"{token}" },
};
return headers;
}
/// <summary>
/// create payment
/// https://developer.paypal.com/docs/api/payments/v1/#payment_create
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static v1.Payment CreatePayment(string token, Payment.Paypal.v1.Payment request)
{
var requestJson = Newtonsoft.Json.JsonConvert.SerializeObject(request);
// $"create请求开始";
var result = PaymentHepler.HttpPost($"{Endpoint}v1/payments/payment/", requestJson, GetHeaders(token));
var response = GetPaypalResult(result);
// $"create响应: {result}";
return response;
}
/// <summary>
/// execute payment
/// https://developer.paypal.com/docs/api/payments/v1/#payment_execute
/// </summary>
/// <param name="payerId"></param>
/// <param name="paymentId"></param>
/// <param name="transactions"></param>
/// <returns></returns>
public static Payment.Paypal.v1.Payment ExecutePayment(string payerId, string paymentId, string token, List<Payment.Paypal.v1.Transaction> transactions)
{
var request = new Dictionary<string, object>();
request.Add("payer_id", payerId);
request.Add("transactions", transactions);
var requestJson = Newtonsoft.Json.JsonConvert.SerializeObject(request);
// $"execute请求开始";
var result = PaymentHepler.HttpPost($"{Endpoint}v1/payments/payment/{paymentId}/execute", requestJson, GetHeaders(token));
// $"execute响应: {paymentId} {result}";
var response = GetPaypalResult(result);
return response;
}
请求支付网关Create Payment
构建请求参数,请求支付网关
/// <summary>
/// 构造交易信息
/// </summary>
/// <returns></returns>
public List<Transaction> BuildTransactions()
{
var transactions = new List<Transaction>();
var transaction = new Transaction()
{
Custom = orderId.ToString(),
InvoiceNumber = orderId.ToString(),
Amount = new Amount()
{
Currency = currencyCode,
Total = amount.ToString(),
Details = new AmountDetails()
{
Subtotal = subTotal.ToString(),
Insurance = insurancePrice.ToString(),
HandlingFee = localReturnService.ToString(),
Shipping = selectedFreightPrice.ToString(),
ShippingDiscount = shippingDiscount.ToString(),
}
},
};
transaction.ItemList = new ItemList()
{
Items = items
};
transaction.ItemList.ShippingAddress = new v1.ShippingAddress()
{
RecipientName = "FirstName" + " " + "LastName",
City = "City",
CountryCode = "CountryCode",
PostalCode = "ZipCode",
Phone = "PhoneNumber",
Line1 = "Addressline1",
Line2 = "Addressline2",
State = "Regin",
}
transactions.Add(transaction);
return transactions;
}
/// <summary>
/// 创建支付请求
/// </summary>
/// <returns></returns>
public static void CreatePayment()
{
var returnUrl = "https://xxx.com/xxxx/Return?id=唯一交易号";
var cancelReturnUrl = "https://xxx.com/xxxx/Detail?id=唯一交易号";
// 构造交易信息
var transactions = this.BuildTransactions();
// paypal请求参数
var paypalRequest = new v1.Payment()
{
Intent = "sale", // 支付模式,及时扣款
RedirectUrls = new RedirectUrls()
{
ReturnUrl = returnUrl, // 同步回调地址
CancelUrl = cancelReturnUrl, // 取消跳转页面
},
Payer = new Payer()
{
PaymentMethod = "paypal"
},
ApplicationContext = new ApplicationContext()
{
LandingPage = "login", // 着陆页面登录页面
ShippingPreference = "SET_PROVIDED_ADDRESS", // 使用传递过去的地址,不能更改
UserAction = "commit", // 提交
},
Transactions = transactions,
};
var token = PaypalUtils.GetAccessToken();
// 创建支付请求
var result = PaypalUtils.CreatePayment(token, paypalRequest);
if (result?.State?.ToLower() == "created")
{
// $"paypal create payment success.";
return status;
}
else
{
// $"paypal create payment failed.";
return status;
}
}
处理同步回传,执行扣款请求Excute Payment
PayPal页面选择卡支付完后,点击commit,跳转至returnUrl,商城接收到参数:
- token
- payerId
- paymentId
#region paypal V1同步处理请求
/// <summary>
/// 同步执行付款操作
/// </summary>
/// <returns></returns>
public ActionResult Return(string id, string token, string payerId, string paymentId)
{
// 验证同步请求
var token = PaypalUtils.GetAccessToken();
// 构造交易信息
var transactions = this.BuildTransactions();
// 执行付款操作
var result = PaypalUtils.ExecutePayment(payerId, paymentId, token, transactions);
return Redirect("支付结果页面");
}
#endregion
定义异步webhook接口,处理异步逻辑
#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 = ""
var requestBody = string.Empty;
using (var reader = new StreamReader(Request.InputStream))
{
requestBody = reader.ReadToEnd();
}
try
{
var isValid = PaypalUtils.ValidateReceivedEvent(requestHeaders, requestBody);
if (isValid)
{
// $"paypal notify is start";
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")
{
// 创建争议
// Todo
}
else if (eventType == "CUSTOMER.DISPUTE.UPDATED")
{
switch (status)
{
case "WAITING_FOR_SELLER_RESPONSE":
// Todo
break;
case "WAITING_FOR_BUYER_RESPONSE":
// Todo
break;
case "UNDER_REVIEW":
// Todo
break;
case "RESOLVED":
// Todo
break;
case "OPEN":
// Todo
break;
default:
// Todo
break;
}
}
else if (eventType == "CUSTOMER.DISPUTE.RESOLVED")
{
// 解决争议
// Todo
}
// Todo
#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")
{
// 退款完成
// Todo
}
#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")
{
// 付款完成
// Todo
}
else if (eventType == "PAYMENT.SALE.PENDING")
{
// 付款pending, 电子支票
// Todo
}
else if (eventType == "PAYMENT.SALE.DENIED")
{
// 付款拒绝
// Todo
}
#endregion
}
}
else
{
// 验签失败
// pay paypal notify valid SignMsg is failed.
return Content("Success");
}
}
catch (Exception ex)
{
//paypal notify, ex: ex.ToString()
}
return Content("Success");
}
至此,PayPal对接创建请求支付,同步回传处理,异步回传处理逻辑代码示例完成,大家可以试一试了
参考PayPal支付流程介绍
上一篇
PayPal支付对接实战:基于Restful API v1的.Net实现

本文档介绍了如何使用.Net进行PayPal支付接口的对接,详细阐述了同步和异步回调流程,以及配置准备、请求授权、创建支付和执行扣款的步骤。通过示例代码,帮助跨境电商平台实现与PayPal的集成。
1601

被折叠的 条评论
为什么被折叠?



