1、接入paypal
2、修改支付宝支付
This commit is contained in:
20
pom.xml
20
pom.xml
@@ -164,7 +164,6 @@
|
|||||||
<version>9.2.1.jre8</version>
|
<version>9.2.1.jre8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- RabbitMQ -->
|
<!-- RabbitMQ -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@@ -184,6 +183,25 @@
|
|||||||
<version>4.22.57.ALL</version>
|
<version>4.22.57.ALL</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!--PayPal SDK-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.paypal.sdk</groupId>
|
||||||
|
<artifactId>checkout-sdk</artifactId>
|
||||||
|
<version>1.0.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.paypal.sdk</groupId>
|
||||||
|
<artifactId>rest-api-sdk</artifactId>
|
||||||
|
<version>LATEST</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.json</groupId>
|
||||||
|
<artifactId>json</artifactId>
|
||||||
|
<version>20230618</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
51
src/main/java/com/ai/da/common/config/PayPalClient.java
Normal file
51
src/main/java/com/ai/da/common/config/PayPalClient.java
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package com.ai.da.common.config;
|
||||||
|
|
||||||
|
import com.paypal.core.PayPalEnvironment;
|
||||||
|
import com.paypal.core.PayPalHttpClient;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
@PropertySource("classpath:paypal-sandbox.properties")
|
||||||
|
public class PayPalClient {
|
||||||
|
|
||||||
|
public PayPalHttpClient client(String mode, String clientId, String clientSecret) {
|
||||||
|
log.info("mode={}, clientId={}, clientSecret={}", mode, clientId, clientSecret);
|
||||||
|
PayPalEnvironment environment = mode.equals("live") ? new PayPalEnvironment.Live(clientId, clientSecret) : new PayPalEnvironment.Sandbox(clientId, clientSecret);
|
||||||
|
return new PayPalHttpClient(environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param jo
|
||||||
|
* @param pre
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
/*public String prettyPrint(JSONObject jo, String pre) {
|
||||||
|
Iterator<?> keys = jo.keys();
|
||||||
|
StringBuilder pretty = new StringBuilder();
|
||||||
|
while (keys.hasNext()) {
|
||||||
|
String key = (String) keys.next();
|
||||||
|
pretty.append(String.format("%s%s: ", pre, StringUtils.capitalize(key)));
|
||||||
|
if (jo.get(key) instanceof JSONObject) {
|
||||||
|
pretty.append(prettyPrint(jo.getJSONObject(key), pre + "\t"));
|
||||||
|
} else if (jo.get(key) instanceof JSONArray) {
|
||||||
|
int sno = 1;
|
||||||
|
for (Object jsonObject : jo.getJSONArray(key)) {
|
||||||
|
pretty.append(String.format("\n%s\t%d:\n", pre, sno++));
|
||||||
|
pretty.append(prettyPrint((JSONObject) jsonObject, pre + "\t\t"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pretty.append(String.format("%s\n", jo.getString(key)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pretty.toString();
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package com.ai.da.common.constant;
|
||||||
|
|
||||||
|
public class PayPalCheckoutConstant {
|
||||||
|
|
||||||
|
public static String MODE = "sandbox";
|
||||||
|
|
||||||
|
public static final String CAPTURE = "CAPTURE";
|
||||||
|
/**
|
||||||
|
* 该标签将覆盖PayPal网站上PayPal帐户中的公司名称
|
||||||
|
*/
|
||||||
|
public static final String BRANDNAME = "AIDA";
|
||||||
|
/**
|
||||||
|
* LOGIN。当客户单击PayPal Checkout时,客户将被重定向到页面以登录PayPal并批准付款。
|
||||||
|
* BILLING。当客户单击PayPal Checkout时,客户将被重定向到一个页面,以输入信用卡或借记卡以及完成购买所需的其他相关账单信息
|
||||||
|
* NO_PREFERENCE。当客户单击“ PayPal Checkout”时,将根据其先前的交互方式将其重定向到页面以登录PayPal并批准付款,或重定向至页面以输入信用卡或借记卡以及完成购买所需的其他相关账单信息使用PayPal。
|
||||||
|
* 默认值:NO_PREFERENCE
|
||||||
|
*/
|
||||||
|
public static final String LANDINGPAGE = "NO_PREFERENCE";
|
||||||
|
/**
|
||||||
|
* CONTINUE。将客户重定向到PayPal付款页面后,将出现“ 继续”按钮。当结帐流程启动时最终金额未知时,请使用此选项,并且您想将客户重定向到商家页面而不处理付款。
|
||||||
|
* PAY_NOW。将客户重定向到PayPal付款页面后,出现“ 立即付款”按钮。当启动结帐时知道最终金额并且您要在客户单击“ 立即付款”时立即处理付款时,请使用此选项。
|
||||||
|
*/
|
||||||
|
public static final String USERACTION = "PAY_NOW";
|
||||||
|
/**
|
||||||
|
* GET_FROM_FILE。使用贝宝网站上客户提供的送货地址。
|
||||||
|
* NO_SHIPPING。从PayPal网站编辑送货地址。推荐用于数字商品
|
||||||
|
* SET_PROVIDED_ADDRESS。使用商家提供的地址。客户无法在PayPal网站上更改此地址
|
||||||
|
*/
|
||||||
|
// public static final String SHIPPINGPREFERENCE = "SET_PROVIDED_ADDRESS";
|
||||||
|
public static final String SHIPPINGPREFERENCE = "NO_SHIPPING";
|
||||||
|
/**
|
||||||
|
* 交易异常
|
||||||
|
*/
|
||||||
|
public static final String FAILURE = "failure";
|
||||||
|
/**
|
||||||
|
* 交易成功
|
||||||
|
*/
|
||||||
|
public static final String SUCCESS = "success";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ipn回调。支付成功
|
||||||
|
*/
|
||||||
|
public static final String PAYMENT_STATUS_COMPLETED = "Completed";
|
||||||
|
/**
|
||||||
|
* ipn回调。退款成功
|
||||||
|
*/
|
||||||
|
public static final String PAYMENT_STATUS_REFUNDED = "Refunded";
|
||||||
|
/**
|
||||||
|
* ipn回调。待定
|
||||||
|
*/
|
||||||
|
public static final String PAYMENT_STATUS_PENDING = "Pending";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ipn回调,付款因退款或其他类型的冲销而被冲销。资金已从您的帐户余额中删除,并退还给买方
|
||||||
|
*/
|
||||||
|
public static final String PAYMENT_STATUS_REVERSED = "Reversed";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ipn回调, 撤销已被取消。例如,您赢得了与客户的纠纷,并且撤回的交易资金已退还给您
|
||||||
|
*/
|
||||||
|
public static final String PAYMENT_STATUS_CANCELED_REVERSAL = "Canceled_Reversal";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ipn回调,付款被拒绝
|
||||||
|
*/
|
||||||
|
public static final String PAYMENT_STATUS_DENIED = "Denied";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ipn回调, 此授权已过期,无法捕获
|
||||||
|
*/
|
||||||
|
public static final String PAYMENT_STATUS_EXPIRED = "Expired";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ipn回调, 德国的ELV付款是通过Express Checkout进行的
|
||||||
|
*/
|
||||||
|
public static final String PAYMENT_STATUS_CREATED = "Created";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ipn回调, 付款失败。仅当付款是通过您客户的银行帐户进行的。
|
||||||
|
*/
|
||||||
|
public static final String PAYMENT_STATUS_FAILED = "Failed";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ipn回调,付款已被接受
|
||||||
|
*/
|
||||||
|
public static final String PAYMENT_STATUS_PROCESSED = "Processed";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ipn回调,此授权已失效
|
||||||
|
*/
|
||||||
|
public static final String PAYMENT_STATUS_VOIDED = "Voided";
|
||||||
|
|
||||||
|
//订单状态
|
||||||
|
/**
|
||||||
|
* 1、支付完成;捕获的付款的资金已记入收款人的PayPal帐户
|
||||||
|
* 2、退款完成;该交易的资金已记入客户的帐户
|
||||||
|
*/
|
||||||
|
public static final String STATE_COMPLETED = "COMPLETED";
|
||||||
|
/**
|
||||||
|
* 部分退款;少于所捕获付款金额的金额已部分退还给付款人。
|
||||||
|
*/
|
||||||
|
public static final String STATE_PARTIALLY_REFUNDED = "PARTIALLY_REFUNDED";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1、支付待定;捕获的付款资金尚未记入收款人的PayPal帐户。有关更多信息请参见status.details。
|
||||||
|
* 2、退款待定;有关更多信息,请参见status_details.reason。
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 支付待定:
|
||||||
|
* capture_status_details
|
||||||
|
* reason 枚举
|
||||||
|
* 捕获的付款状态为PENDING或DENIED的原因。可能的值为:
|
||||||
|
* BUYER_COMPLAINT。付款人与贝宝(PayPal)对此捕获的付款提出了争议。
|
||||||
|
* CHARGEBACK。响应于付款人与用于支付此已捕获付款的金融工具的发行人对此已捕获的付款提出异议,已收回的资金被撤回。
|
||||||
|
* ECHECK。由尚未结清的电子支票支付的付款人。
|
||||||
|
* INTERNATIONAL_WITHDRAWAL。访问您的在线帐户。在您的“帐户概览”中,接受并拒绝此笔付款。
|
||||||
|
* OTHER。无法提供其他特定原因。有关此笔付款的更多信息,请在线访问您的帐户或联系PayPal。
|
||||||
|
* PENDING_REVIEW。捕获的付款正在等待人工审核。
|
||||||
|
*(手动收取)RECEIVING_PREFERENCE_MANDATES_MANUAL_ACTION。收款人尚未为其帐户设置适当的接收首选项。有关如何接受或拒绝此付款的更多信息,请在线访问您的帐户。通常在某些情况下提供此原因,例如,当所捕获付款的货币与收款人的主要持有货币不同时。
|
||||||
|
* REFUNDED。收回的资金已退还。
|
||||||
|
* TRANSACTION_APPROVED_AWAITING_FUNDING。付款人必须将这笔付款的资金汇出。通常,此代码适用于手动EFT。
|
||||||
|
* UNILATERAL。收款人没有PayPal帐户。
|
||||||
|
* VERIFICATION_REQUIRED。收款人的PayPal帐户未通过验证。
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 退款待定
|
||||||
|
* 退款具有“PENDING”或“FAILED”状态的原因。 可能的值为:
|
||||||
|
* ECHECK。客户的帐户通过尚未结清的eCheck进行注资。
|
||||||
|
*/
|
||||||
|
public static final String STATE_PENDING = "PENDING";
|
||||||
|
/**
|
||||||
|
* 退款;大于或等于此捕获的付款金额的金额已退还给付款人
|
||||||
|
*/
|
||||||
|
public static final String STATE_REFUNDED = "REFUNDED";
|
||||||
|
/**
|
||||||
|
* 支付拒绝
|
||||||
|
*/
|
||||||
|
public static final String STATE_DENIED = "DENIED";
|
||||||
|
/**
|
||||||
|
* 退款失败
|
||||||
|
*/
|
||||||
|
public static final String STATE_FAILED = "FAILED";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 争议状态
|
||||||
|
*/
|
||||||
|
public static final String BUYER_COMPLAINT = "BUYER_COMPLAINT";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 沙箱环境请求网关地址
|
||||||
|
*/
|
||||||
|
public static final String SANDBOX = "https://api.sandbox.paypal.com";
|
||||||
|
/**
|
||||||
|
* 生产环境请求网关地址
|
||||||
|
*/
|
||||||
|
public static final String LIVE = "https://api.paypal.com";
|
||||||
|
/**
|
||||||
|
* 添加物流信息请求路径
|
||||||
|
*/
|
||||||
|
public static final String ADD_TRACK_URL = "/v1/shipping/trackers-batch";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改物流信息请求路径
|
||||||
|
*/
|
||||||
|
public static final String UPDATE_TRACK_URL = "/v1/shipping/trackers/";
|
||||||
|
|
||||||
|
public final static String CMD_NOTIFY_VALIDATE = "_notify-validate";
|
||||||
|
|
||||||
|
public final static String WEBHOOK_ID = "31797347YC028794L";
|
||||||
|
|
||||||
|
public final static String PAYPAL_TOKEN_KEY = "PayPalAccessToken";
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
40
src/main/java/com/ai/da/common/enums/CurrencyCodesEnum.java
Normal file
40
src/main/java/com/ai/da/common/enums/CurrencyCodesEnum.java
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package com.ai.da.common.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum CurrencyCodesEnum {
|
||||||
|
|
||||||
|
AUSTRALIAN_DOLLAR("AUD"),
|
||||||
|
BRAZILIAN_REAL("BRL"),
|
||||||
|
CANADIAN_DOLLAR("CAD"),
|
||||||
|
CHINESE_RENMENBI("CNY"),
|
||||||
|
CZECH_KORUNA("CZK"),
|
||||||
|
DANISH_KRONE("DKK"),
|
||||||
|
EURO("EUR"),
|
||||||
|
HONG_KONG_DOLLAR("HKD"),
|
||||||
|
HUNGARIAN_FORINT("HUF"),
|
||||||
|
ISRAELI_NEW_SHEKEL("ILS"),
|
||||||
|
JAPANESE_YEN("JPY"),
|
||||||
|
MALAYSIAN_RINGGIT("MYR"),
|
||||||
|
MEXICAN_PESO("MXN"),
|
||||||
|
NEW_TAIWAN_DOLLAR("TWD"),
|
||||||
|
NEW_ZEALAND_DOLLAR("NZD"),
|
||||||
|
NORWEGIAN_KRONE("NOK"),
|
||||||
|
PHILIPPINE_PESO("PHP"),
|
||||||
|
POLISH_ZLOTY("PLN"),
|
||||||
|
POUND_STERLING("GBP"),
|
||||||
|
RUSSIAN_RUBLE("RUB"),
|
||||||
|
SINGAPORE_DOLLAR("SGD"),
|
||||||
|
SWEDISH_KRONA("SEK"),
|
||||||
|
SWISS_FRANC("CHF"),
|
||||||
|
THAI_BAHT("THB"),
|
||||||
|
UNITED_STATES_DOLLAR("USD");
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
CurrencyCodesEnum(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ public enum OrderStatusEnum {
|
|||||||
/**
|
/**
|
||||||
* 已关闭
|
* 已关闭
|
||||||
*/
|
*/
|
||||||
CLOSED("超时已关闭"),
|
TIMEOUT_CLOSED("超时已关闭"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 已取消
|
* 已取消
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.ai.da.common.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum PayPalOrderStatusEnum {
|
||||||
|
|
||||||
|
CREATED("CREATED"),
|
||||||
|
|
||||||
|
SAVED("SAVED"),
|
||||||
|
|
||||||
|
APPROVED("APPROVED"),
|
||||||
|
|
||||||
|
VOIDED("VOIDED"),
|
||||||
|
|
||||||
|
COMPLETED("COMPLETED"),
|
||||||
|
|
||||||
|
PAYER_ACTION_REQUIRED("PAYER_ACTION_REQUIRED");
|
||||||
|
|
||||||
|
|
||||||
|
private final String status;
|
||||||
|
}
|
||||||
@@ -11,11 +11,15 @@ public enum PayTypeEnum {
|
|||||||
*/
|
*/
|
||||||
WXPAY("微信"),
|
WXPAY("微信"),
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付宝
|
* 支付宝
|
||||||
*/
|
*/
|
||||||
ALIPAY("支付宝");
|
ALIPAY("支付宝"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayPal
|
||||||
|
*/
|
||||||
|
PAYPAL("PayPal");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类型
|
* 类型
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class AuthenticationFilter extends OncePerRequestFilter {
|
|||||||
"/api/third/party/addNoLoginRequiredNew","/api/third/party/deleteNoLoginRequiredNew",
|
"/api/third/party/addNoLoginRequiredNew","/api/third/party/deleteNoLoginRequiredNew",
|
||||||
"/api/third/party/existNoLoginRequired","/api/third/party/getRedirectUrl",
|
"/api/third/party/existNoLoginRequired","/api/third/party/getRedirectUrl",
|
||||||
// "/api/python/chatStream",
|
// "/api/python/chatStream",
|
||||||
"/api/python/flush","/api/account/healthy","/api/ali-pay/trade/notify"
|
"/api/python/flush","/api/account/healthy","/api/ali-pay/trade/notify","/api/paypal/ipn/back"
|
||||||
);
|
);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class AliPayTask {
|
|||||||
|
|
||||||
log.info("orderConfirm 被执行......");
|
log.info("orderConfirm 被执行......");
|
||||||
|
|
||||||
List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(1, PayTypeEnum.ALIPAY.getType());
|
List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(5, PayTypeEnum.ALIPAY.getType());
|
||||||
|
|
||||||
for (OrderInfo orderInfo : orderInfoList) {
|
for (OrderInfo orderInfo : orderInfoList) {
|
||||||
String orderNo = orderInfo.getOrderNo();
|
String orderNo = orderInfo.getOrderNo();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.springframework.util.CollectionUtils;
|
|||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@@ -17,6 +18,10 @@ public class RedisUtil {
|
|||||||
@Resource
|
@Resource
|
||||||
private RedisTemplate<String, String> redisTemplate;
|
private RedisTemplate<String, String> redisTemplate;
|
||||||
|
|
||||||
|
public Boolean hasKey(String key){
|
||||||
|
return redisTemplate.hasKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
//- - - - - - - - - - - - - - - - - - - - - ZSet类型 - - - - - - - - - - - - - - - - - - - -
|
//- - - - - - - - - - - - - - - - - - - - - ZSet类型 - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,4 +128,14 @@ public class RedisUtil {
|
|||||||
public Long removeFromMap(String key, String hashKeys) {
|
public Long removeFromMap(String key, String hashKeys) {
|
||||||
return redisTemplate.opsForHash().delete(key, hashKeys);
|
return redisTemplate.opsForHash().delete(key, hashKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//- - - - - - - - - - - - - - - - - - - - - String类型 - - - - - - - - - - - - - - - - - - - -
|
||||||
|
public void addToString(String key, String value, Long expiresIn){
|
||||||
|
redisTemplate.opsForValue().set(key,value,expiresIn, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFromString(String key){
|
||||||
|
return redisTemplate.opsForValue().get(key);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.ai.da.common.utils.paypalRequest;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.paypal.http.HttpRequest;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class AuthenticationRequest extends HttpRequest<HashMap> {
|
||||||
|
|
||||||
|
public AuthenticationRequest() {
|
||||||
|
super("/v1/oauth2/token", "POST", HashMap.class);
|
||||||
|
this.header("Content-Type", "application/x-www-form-urlencoded");
|
||||||
|
body();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationRequest authorization(String clientId, String clientSecret) {
|
||||||
|
this.header(clientId, clientSecret);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationRequest body() {
|
||||||
|
HashMap<String, String> body = new HashMap<>();
|
||||||
|
body.put("grant_type", "client_credentials");
|
||||||
|
this.requestBody(body);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.ai.da.common.utils.paypalRequest;
|
||||||
|
|
||||||
|
import com.ai.da.model.dto.WebhookVerifyDTO;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.paypal.http.HttpRequest;
|
||||||
|
import com.paypal.orders.OrdersCreateRequest;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class WebhookVerifyRequest extends HttpRequest<HashMap> {
|
||||||
|
public WebhookVerifyRequest() {
|
||||||
|
super("/v1/notifications/verify-webhook-signature", "POST", HashMap.class);
|
||||||
|
this.header("Content-Type", "application/json");
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebhookVerifyRequest authorization(String authorization) {
|
||||||
|
this.header("Authorization", "Bearer " + String.valueOf(authorization));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebhookVerifyRequest requestBody(HashMap<String,String> webhookVerify) {
|
||||||
|
super.requestBody(webhookVerify);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,13 @@
|
|||||||
package com.ai.da.controller;
|
package com.ai.da.controller;
|
||||||
|
|
||||||
import com.ai.da.common.response.Response;
|
import com.ai.da.common.response.Response;
|
||||||
import com.ai.da.mapper.primary.entity.OrderInfo;
|
|
||||||
import com.ai.da.service.AliPayService;
|
import com.ai.da.service.AliPayService;
|
||||||
import com.ai.da.service.OrderInfoService;
|
|
||||||
import com.alipay.api.AlipayApiException;
|
|
||||||
import com.alipay.api.AlipayConstants;
|
|
||||||
import com.alipay.api.internal.util.AlipaySignature;
|
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.core.env.Environment;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@CrossOrigin
|
@CrossOrigin
|
||||||
@@ -27,18 +20,9 @@ public class AliPayController {
|
|||||||
@Resource
|
@Resource
|
||||||
private AliPayService aliPayService;
|
private AliPayService aliPayService;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private Environment config;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private OrderInfoService orderInfoService;
|
|
||||||
|
|
||||||
@ApiOperation("统一收单下单并支付页面接口的调用")
|
@ApiOperation("统一收单下单并支付页面接口的调用")
|
||||||
@PostMapping("/trade/page/pay/{productId}")
|
@PostMapping("/trade/page/pay/{productId}")
|
||||||
public Response<String> tradePagePay(@PathVariable Long productId,@RequestParam String returnUrl){
|
public Response<String> tradePagePay(@PathVariable Long productId,@RequestParam String returnUrl){
|
||||||
|
|
||||||
System.out.println(productId + " " + returnUrl);
|
|
||||||
|
|
||||||
log.info("统一收单下单并支付页面接口的调用");
|
log.info("统一收单下单并支付页面接口的调用");
|
||||||
//支付宝开放平台接受 request 请求对象后
|
//支付宝开放平台接受 request 请求对象后
|
||||||
// 会为开发者生成一个html 形式的 form表单,包含自动提交的脚本
|
// 会为开发者生成一个html 形式的 form表单,包含自动提交的脚本
|
||||||
@@ -51,80 +35,7 @@ public class AliPayController {
|
|||||||
@ApiOperation("支付通知")
|
@ApiOperation("支付通知")
|
||||||
@PostMapping("/trade/notify")
|
@PostMapping("/trade/notify")
|
||||||
public String tradeNotify(@RequestParam Map<String, String> params){
|
public String tradeNotify(@RequestParam Map<String, String> params){
|
||||||
|
return aliPayService.tradeNotify(params);
|
||||||
log.info("支付通知正在执行");
|
|
||||||
log.info("通知参数 ===> {}", params);
|
|
||||||
|
|
||||||
String result = "failure";
|
|
||||||
|
|
||||||
try {
|
|
||||||
//异步通知验签
|
|
||||||
boolean signVerified = AlipaySignature.rsaCheckV1(
|
|
||||||
params,
|
|
||||||
config.getProperty("alipay.alipay-public-key"),
|
|
||||||
AlipayConstants.CHARSET_UTF8,
|
|
||||||
AlipayConstants.SIGN_TYPE_RSA2); //调用SDK验证签名
|
|
||||||
|
|
||||||
if(!signVerified){
|
|
||||||
//验签失败则记录异常日志,并在response中返回failure.
|
|
||||||
log.error("支付成功异步通知验签失败!");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验签成功后
|
|
||||||
log.info("支付成功异步通知验签成功!");
|
|
||||||
|
|
||||||
//按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,
|
|
||||||
//1 商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号
|
|
||||||
String outTradeNo = params.get("out_trade_no");
|
|
||||||
OrderInfo order = orderInfoService.getOrderByOrderNo(outTradeNo);
|
|
||||||
if(order == null){
|
|
||||||
log.error("订单不存在");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
//2 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额)
|
|
||||||
String totalAmount = params.get("total_amount");
|
|
||||||
int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
|
|
||||||
int totalFeeInt = order.getTotalFee().intValue();
|
|
||||||
if(totalAmountInt != totalFeeInt){
|
|
||||||
log.error("金额校验失败");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
//3 校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no 这笔单据的对应的操作方
|
|
||||||
String sellerId = params.get("seller_id");
|
|
||||||
String sellerIdProperty = config.getProperty("alipay.seller-id");
|
|
||||||
if(!sellerId.equals(sellerIdProperty)){
|
|
||||||
log.error("商家pid校验失败");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
//4 验证 app_id 是否为该商户本身
|
|
||||||
String appId = params.get("app_id");
|
|
||||||
String appIdProperty = config.getProperty("alipay.app-id");
|
|
||||||
if(!appId.equals(appIdProperty)){
|
|
||||||
log.error("appid校验失败");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
//在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS时,
|
|
||||||
// 支付宝才会认定为买家付款成功。
|
|
||||||
String tradeStatus = params.get("trade_status");
|
|
||||||
if(!"TRADE_SUCCESS".equals(tradeStatus)){
|
|
||||||
log.error("支付未成功");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
//处理业务 修改订单状态 记录支付日志
|
|
||||||
aliPayService.processOrder(params);
|
|
||||||
|
|
||||||
//校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure
|
|
||||||
result = "success";
|
|
||||||
} catch (AlipayApiException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,7 +46,6 @@ public class AliPayController {
|
|||||||
@ApiOperation("用户取消订单")
|
@ApiOperation("用户取消订单")
|
||||||
@PostMapping("/trade/close/{orderNo}")
|
@PostMapping("/trade/close/{orderNo}")
|
||||||
public Response<String> cancel(@PathVariable String orderNo){
|
public Response<String> cancel(@PathVariable String orderNo){
|
||||||
|
|
||||||
log.info("取消订单");
|
log.info("取消订单");
|
||||||
aliPayService.cancelOrder(orderNo);
|
aliPayService.cancelOrder(orderNo);
|
||||||
return Response.success("订单已取消");
|
return Response.success("订单已取消");
|
||||||
@@ -149,9 +59,7 @@ public class AliPayController {
|
|||||||
@ApiOperation("查询订单:测试订单状态用")
|
@ApiOperation("查询订单:测试订单状态用")
|
||||||
@GetMapping("/trade/query/{orderNo}")
|
@GetMapping("/trade/query/{orderNo}")
|
||||||
public Response<String> queryOrder(@PathVariable String orderNo) {
|
public Response<String> queryOrder(@PathVariable String orderNo) {
|
||||||
|
|
||||||
log.info("查询订单");
|
log.info("查询订单");
|
||||||
|
|
||||||
String result = aliPayService.queryOrder(orderNo);
|
String result = aliPayService.queryOrder(orderNo);
|
||||||
return Response.success(result);
|
return Response.success(result);
|
||||||
|
|
||||||
@@ -166,7 +74,6 @@ public class AliPayController {
|
|||||||
@ApiOperation("申请退款")
|
@ApiOperation("申请退款")
|
||||||
@PostMapping("/trade/refund/{orderNo}/{reason}")
|
@PostMapping("/trade/refund/{orderNo}/{reason}")
|
||||||
public Response<String> refunds(@PathVariable String orderNo, @PathVariable String reason){
|
public Response<String> refunds(@PathVariable String orderNo, @PathVariable String reason){
|
||||||
|
|
||||||
log.info("申请退款");
|
log.info("申请退款");
|
||||||
aliPayService.refund(orderNo, reason);
|
aliPayService.refund(orderNo, reason);
|
||||||
return Response.success();
|
return Response.success();
|
||||||
@@ -180,10 +87,8 @@ public class AliPayController {
|
|||||||
*/
|
*/
|
||||||
@ApiOperation("查询退款:测试用")
|
@ApiOperation("查询退款:测试用")
|
||||||
@GetMapping("/trade/fastpay/refund/{orderNo}")
|
@GetMapping("/trade/fastpay/refund/{orderNo}")
|
||||||
public Response<String> queryRefund(@PathVariable String orderNo) throws Exception {
|
public Response<String> queryRefund(@PathVariable String orderNo) {
|
||||||
|
|
||||||
log.info("查询退款");
|
log.info("查询退款");
|
||||||
|
|
||||||
String result = aliPayService.queryRefund(orderNo);
|
String result = aliPayService.queryRefund(orderNo);
|
||||||
return Response.success(result);
|
return Response.success(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.ai.da.controller;
|
package com.ai.da.controller;
|
||||||
|
|
||||||
|
import com.ai.da.common.context.UserContext;
|
||||||
import com.ai.da.common.enums.OrderStatusEnum;
|
import com.ai.da.common.enums.OrderStatusEnum;
|
||||||
import com.ai.da.common.response.Response;
|
import com.ai.da.common.response.Response;
|
||||||
import com.ai.da.mapper.primary.entity.OrderInfo;
|
import com.ai.da.mapper.primary.entity.OrderInfo;
|
||||||
@@ -23,9 +24,9 @@ public class OrderInfoController {
|
|||||||
@ApiOperation("订单列表")
|
@ApiOperation("订单列表")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public Response<List<OrderInfo>> list(){
|
public Response<List<OrderInfo>> list(){
|
||||||
|
List<OrderInfo> orderByAccountId = orderInfoService.getOrderByAccountId(UserContext.getUserHolder().getId());
|
||||||
List<OrderInfo> list = orderInfoService.listOrderByCreateTimeDesc();
|
// List<OrderInfo> list = orderInfoService.listOrderByCreateTimeDesc();
|
||||||
return Response.success(list);
|
return Response.success(orderByAccountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,15 +37,11 @@ public class OrderInfoController {
|
|||||||
@ApiOperation("查询本地订单状态")
|
@ApiOperation("查询本地订单状态")
|
||||||
@GetMapping("/query-order-status/{orderNo}")
|
@GetMapping("/query-order-status/{orderNo}")
|
||||||
public Response<String> queryOrderStatus(@PathVariable String orderNo){
|
public Response<String> queryOrderStatus(@PathVariable String orderNo){
|
||||||
|
|
||||||
String orderStatus = orderInfoService.getOrderStatus(orderNo);
|
String orderStatus = orderInfoService.getOrderStatus(orderNo);
|
||||||
if(OrderStatusEnum.SUCCESS.getType().equals(orderStatus)){
|
if(OrderStatusEnum.SUCCESS.getType().equals(orderStatus)){
|
||||||
return Response.success("支付成功"); //支付成功
|
return Response.success("支付成功"); //支付成功
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.success(101,"支付中......");
|
return Response.success(101,"支付中......");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.ai.da.controller;
|
||||||
|
|
||||||
|
import com.ai.da.common.response.Response;
|
||||||
|
import com.ai.da.service.CallBackService;
|
||||||
|
import com.ai.da.service.PayPalCheckoutService;
|
||||||
|
import com.paypal.http.HttpResponse;
|
||||||
|
import com.paypal.http.exceptions.SerializeException;
|
||||||
|
import com.paypal.orders.Order;
|
||||||
|
import com.paypal.payments.Refund;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Api(tags = "PayPalCheckout接口")
|
||||||
|
@RequestMapping("/api/paypal")
|
||||||
|
public class PayPalCheckoutController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PayPalCheckoutService payPalCheckoutService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CallBackService callBackService;
|
||||||
|
|
||||||
|
@ApiOperation(value = "创建订单")
|
||||||
|
@PostMapping(value = "/trade/{productId}")
|
||||||
|
public Response<HashMap<String, String>> createOrder(@PathVariable Long productId,@RequestParam String returnUrl) throws SerializeException {
|
||||||
|
HashMap<String, String> approvalUrl = payPalCheckoutService.createOrder(productId,returnUrl);
|
||||||
|
return Response.success(approvalUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation(value = "ipn异步回调")
|
||||||
|
@PostMapping(value = "/ipn/back")
|
||||||
|
public Response<String> callback(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
|
Boolean result = callBackService.doGet(request, response);
|
||||||
|
if (result){
|
||||||
|
return Response.success();
|
||||||
|
}else {
|
||||||
|
return Response.fail(500,"failure");
|
||||||
|
}
|
||||||
|
// return payPalCheckoutService.callback(RequestToMapUtil.getParameterMap(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation(value = "查询指定订单")
|
||||||
|
@PostMapping(value = "/trade/query/{orderNo}")
|
||||||
|
public Response<String> queryOrder(@PathVariable String orderNo) throws SerializeException {
|
||||||
|
String s = payPalCheckoutService.queryOrder(orderNo);
|
||||||
|
return Response.success(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("申请退款")
|
||||||
|
@PostMapping("/trade/refund/{orderNo}/{reason}")
|
||||||
|
public Response<HttpResponse<Refund>> refund(@PathVariable String orderNo, @PathVariable String reason) throws IOException {
|
||||||
|
Boolean response = payPalCheckoutService.refundOrder(orderNo,reason);
|
||||||
|
if (response){
|
||||||
|
return Response.success();
|
||||||
|
}else {
|
||||||
|
return Response.fail("Request for refund failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("执行扣款")
|
||||||
|
@PostMapping("/trade/capture/{orderNo}")
|
||||||
|
public Response<com.paypal.orders.Order> captureOrder(@PathVariable String orderNo) throws IOException {
|
||||||
|
Order response = payPalCheckoutService.captureOrder(orderNo);
|
||||||
|
return Response.success(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -11,11 +11,11 @@ public class OrderInfo extends BaseEntity{
|
|||||||
|
|
||||||
private String orderNo;//商户订单编号
|
private String orderNo;//商户订单编号
|
||||||
|
|
||||||
private Long userId;//用户id
|
private Long accountId;//用户id
|
||||||
|
|
||||||
private Long productId;//支付产品id
|
private Long productId;//支付产品id
|
||||||
|
|
||||||
private Integer totalFee;//订单金额(分)
|
private Integer totalFee;//订单金额(元)
|
||||||
|
|
||||||
private String codeUrl;//订单二维码连接
|
private String codeUrl;//订单二维码连接
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class RefundInfo extends BaseEntity{
|
|||||||
|
|
||||||
private String refundNo;//退款单编号
|
private String refundNo;//退款单编号
|
||||||
|
|
||||||
private String refundId;//支付系统退款单号
|
private String refundId;//支付系统退款单号(微信)
|
||||||
|
|
||||||
private Integer totalFee;//原订单金额(分)
|
private Integer totalFee;//原订单金额(分)
|
||||||
|
|
||||||
|
|||||||
35
src/main/java/com/ai/da/model/dto/WebhookVerifyDTO.java
Normal file
35
src/main/java/com/ai/da/model/dto/WebhookVerifyDTO.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package com.ai.da.model.dto;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class WebhookVerifyDTO implements Serializable {
|
||||||
|
|
||||||
|
private String transmission_id;
|
||||||
|
private String transmission_time;
|
||||||
|
private String cert_url;
|
||||||
|
private String auth_algo;
|
||||||
|
private String transmission_sig;
|
||||||
|
private String webhook_id;
|
||||||
|
private Object webhook_event;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -5,6 +5,8 @@ import java.util.Map;
|
|||||||
public interface AliPayService {
|
public interface AliPayService {
|
||||||
String tradeCreate(Long productId,String returnUrl);
|
String tradeCreate(Long productId,String returnUrl);
|
||||||
|
|
||||||
|
String tradeNotify(Map<String, String> params);
|
||||||
|
|
||||||
void processOrder(Map<String, String> params);
|
void processOrder(Map<String, String> params);
|
||||||
|
|
||||||
void cancelOrder(String orderNo);
|
void cancelOrder(String orderNo);
|
||||||
|
|||||||
12
src/main/java/com/ai/da/service/CallBackService.java
Normal file
12
src/main/java/com/ai/da/service/CallBackService.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package com.ai.da.service;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface CallBackService {
|
||||||
|
|
||||||
|
Boolean doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||||
|
throws ServletException, IOException;
|
||||||
|
}
|
||||||
@@ -22,4 +22,8 @@ public interface OrderInfoService extends IService<OrderInfo> {
|
|||||||
List<OrderInfo> getNoPayOrderByDuration(int minutes, String paymentType);
|
List<OrderInfo> getNoPayOrderByDuration(int minutes, String paymentType);
|
||||||
|
|
||||||
OrderInfo getOrderByOrderNo(String orderNo);
|
OrderInfo getOrderByOrderNo(String orderNo);
|
||||||
|
|
||||||
|
List<OrderInfo> getOrderByAccountId(Long accountId);
|
||||||
|
|
||||||
|
void updateOrderNoById(Long id, String orderNo);
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/main/java/com/ai/da/service/PayPalCheckoutService.java
Normal file
29
src/main/java/com/ai/da/service/PayPalCheckoutService.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package com.ai.da.service;
|
||||||
|
|
||||||
|
import com.paypal.http.exceptions.SerializeException;
|
||||||
|
import com.paypal.orders.Order;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface PayPalCheckoutService {
|
||||||
|
|
||||||
|
HashMap<String, String> createOrder(Long productId,String returnUrl) throws SerializeException;
|
||||||
|
/**
|
||||||
|
* 回调
|
||||||
|
* @param map
|
||||||
|
*/
|
||||||
|
String callback(@SuppressWarnings("rawtypes") Map map);
|
||||||
|
|
||||||
|
String queryOrder(String orderNo) throws SerializeException;
|
||||||
|
|
||||||
|
Order captureOrder(String orderId) throws IOException;
|
||||||
|
|
||||||
|
Boolean refundOrder(String orderId, String reason) throws IOException;
|
||||||
|
|
||||||
|
String getOAuth();
|
||||||
|
|
||||||
|
void processOrder(String orderId);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.ai.da.service;
|
package com.ai.da.service;
|
||||||
|
|
||||||
|
import com.paypal.orders.Order;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public interface PaymentInfoService {
|
public interface PaymentInfoService {
|
||||||
@@ -7,4 +9,6 @@ public interface PaymentInfoService {
|
|||||||
void createPaymentInfo(String plainText);
|
void createPaymentInfo(String plainText);
|
||||||
|
|
||||||
void createPaymentInfoForAliPay(Map<String, String> params);
|
void createPaymentInfoForAliPay(Map<String, String> params);
|
||||||
|
|
||||||
|
void createPaymentInfoForPayPal(Order order);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,4 +17,6 @@ public interface RefundInfoService extends IService<RefundInfo> {
|
|||||||
RefundInfo createRefundByOrderNoForAliPay(String orderNo, String reason);
|
RefundInfo createRefundByOrderNoForAliPay(String orderNo, String reason);
|
||||||
|
|
||||||
void updateRefundForAliPay(String refundNo, String content, String refundStatus);
|
void updateRefundForAliPay(String refundNo, String content, String refundStatus);
|
||||||
|
|
||||||
|
void updateRefundForPayPal(Long id, String refundId, String content, String refundStatus);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.ai.da.service.impl;
|
package com.ai.da.service.impl;
|
||||||
|
|
||||||
|
import com.ai.da.common.config.exception.BusinessException;
|
||||||
import com.ai.da.common.enums.AliPayTradeStateEnum;
|
import com.ai.da.common.enums.AliPayTradeStateEnum;
|
||||||
import com.ai.da.common.enums.OrderStatusEnum;
|
import com.ai.da.common.enums.OrderStatusEnum;
|
||||||
import com.ai.da.common.enums.PayTypeEnum;
|
import com.ai.da.common.enums.PayTypeEnum;
|
||||||
@@ -12,6 +13,8 @@ import com.ai.da.service.RefundInfoService;
|
|||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.alipay.api.AlipayApiException;
|
import com.alipay.api.AlipayApiException;
|
||||||
import com.alipay.api.AlipayClient;
|
import com.alipay.api.AlipayClient;
|
||||||
|
import com.alipay.api.AlipayConstants;
|
||||||
|
import com.alipay.api.internal.util.AlipaySignature;
|
||||||
import com.alipay.api.request.*;
|
import com.alipay.api.request.*;
|
||||||
import com.alipay.api.response.*;
|
import com.alipay.api.response.*;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
@@ -69,7 +72,7 @@ public class AliPayServiceImpl implements AliPayService {
|
|||||||
//组装当前业务方法的请求参数
|
//组装当前业务方法的请求参数
|
||||||
JSONObject bizContent = new JSONObject();
|
JSONObject bizContent = new JSONObject();
|
||||||
bizContent.put("out_trade_no", orderInfo.getOrderNo());
|
bizContent.put("out_trade_no", orderInfo.getOrderNo());
|
||||||
BigDecimal total = new BigDecimal(orderInfo.getTotalFee().toString()).divide(new BigDecimal("100"));
|
BigDecimal total = new BigDecimal(orderInfo.getTotalFee().toString());
|
||||||
bizContent.put("total_amount", total);
|
bizContent.put("total_amount", total);
|
||||||
bizContent.put("subject", orderInfo.getTitle());
|
bizContent.put("subject", orderInfo.getTitle());
|
||||||
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
|
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
|
||||||
@@ -84,14 +87,93 @@ public class AliPayServiceImpl implements AliPayService {
|
|||||||
return response.getBody();
|
return response.getBody();
|
||||||
} else {
|
} else {
|
||||||
log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
|
log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
|
||||||
throw new RuntimeException("创建支付交易失败");
|
throw new BusinessException("Order creation failed");
|
||||||
}
|
}
|
||||||
} catch (AlipayApiException e) {
|
} catch (AlipayApiException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException("创建支付交易失败");
|
throw new BusinessException("Order creation failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String tradeNotify(Map<String, String> params) {
|
||||||
|
log.info("支付通知正在执行");
|
||||||
|
log.info("通知参数 ===> {}", params);
|
||||||
|
|
||||||
|
String result = "failure";
|
||||||
|
|
||||||
|
try {
|
||||||
|
//异步通知验签
|
||||||
|
boolean signVerified = AlipaySignature.rsaCheckV1(
|
||||||
|
params,
|
||||||
|
config.getProperty("alipay.alipay-public-key"),
|
||||||
|
AlipayConstants.CHARSET_UTF8,
|
||||||
|
AlipayConstants.SIGN_TYPE_RSA2); //调用SDK验证签名
|
||||||
|
|
||||||
|
if(!signVerified){
|
||||||
|
//验签失败则记录异常日志,并在response中返回failure.
|
||||||
|
log.error("支付成功异步通知验签失败!");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验签成功后
|
||||||
|
log.info("支付成功异步通知验签成功!");
|
||||||
|
|
||||||
|
//按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,
|
||||||
|
//1 商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号
|
||||||
|
String outTradeNo = params.get("out_trade_no");
|
||||||
|
OrderInfo order = orderInfoService.getOrderByOrderNo(outTradeNo);
|
||||||
|
if(order == null){
|
||||||
|
log.error("订单不存在");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//2 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额)
|
||||||
|
String totalAmount = params.get("total_amount");
|
||||||
|
int totalAmountInt = new BigDecimal(totalAmount).intValue();
|
||||||
|
int totalFeeInt = order.getTotalFee().intValue();
|
||||||
|
if(totalAmountInt != totalFeeInt){
|
||||||
|
log.error("金额校验失败");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//3 校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no 这笔单据的对应的操作方
|
||||||
|
String sellerId = params.get("seller_id");
|
||||||
|
String sellerIdProperty = config.getProperty("alipay.seller-id");
|
||||||
|
if(!sellerId.equals(sellerIdProperty)){
|
||||||
|
log.error("商家pid校验失败");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//4 验证 app_id 是否为该商户本身
|
||||||
|
String appId = params.get("app_id");
|
||||||
|
String appIdProperty = config.getProperty("alipay.app-id");
|
||||||
|
if(!appId.equals(appIdProperty)){
|
||||||
|
log.error("appid校验失败");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS时,
|
||||||
|
// 支付宝才会认定为买家付款成功。
|
||||||
|
String tradeStatus = params.get("trade_status");
|
||||||
|
if(!"TRADE_SUCCESS".equals(tradeStatus)){
|
||||||
|
log.error("支付未成功");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//处理业务 修改订单状态 记录支付日志
|
||||||
|
processOrder(params);
|
||||||
|
|
||||||
|
//校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure
|
||||||
|
result = "success";
|
||||||
|
} catch (AlipayApiException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理订单
|
* 处理订单
|
||||||
* @param params
|
* @param params
|
||||||
@@ -112,26 +194,22 @@ public class AliPayServiceImpl implements AliPayService {
|
|||||||
// 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放
|
// 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放
|
||||||
if(lock.tryLock()) {
|
if(lock.tryLock()) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
//处理重复通知
|
//处理重复通知
|
||||||
//接口调用的幂等性:无论接口被调用多少次,以下业务执行一次
|
//接口调用的幂等性:无论接口被调用多少次,以下业务执行一次
|
||||||
String orderStatus = orderInfoService.getOrderStatus(orderNo);
|
String orderStatus = orderInfoService.getOrderStatus(orderNo);
|
||||||
if (!OrderStatusEnum.NOT_PAY.getType().equals(orderStatus)) {
|
// 当订单状态处于未支付或超时已关闭时,更新订单状态
|
||||||
|
if (!OrderStatusEnum.NOT_PAY.getType().equals(orderStatus) || !OrderStatusEnum.TIMEOUT_CLOSED.getType().equals(orderStatus)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//更新订单状态
|
//更新订单状态
|
||||||
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS);
|
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS);
|
||||||
|
|
||||||
//记录支付日志
|
//记录支付日志
|
||||||
paymentInfoService.createPaymentInfoForAliPay(params);
|
paymentInfoService.createPaymentInfoForAliPay(params);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
//要主动释放锁
|
//要主动释放锁
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -176,7 +254,7 @@ public class AliPayServiceImpl implements AliPayService {
|
|||||||
|
|
||||||
} catch (AlipayApiException e) {
|
} catch (AlipayApiException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException("查单接口的调用失败");
|
throw new BusinessException("查单接口的调用失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +276,7 @@ public class AliPayServiceImpl implements AliPayService {
|
|||||||
if(result == null){
|
if(result == null){
|
||||||
log.warn("核实订单未创建 ===> {}", orderNo);
|
log.warn("核实订单未创建 ===> {}", orderNo);
|
||||||
//更新本地订单状态
|
//更新本地订单状态
|
||||||
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.CLOSED);
|
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.TIMEOUT_CLOSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
//解析查单响应结果
|
//解析查单响应结果
|
||||||
@@ -209,20 +287,16 @@ public class AliPayServiceImpl implements AliPayService {
|
|||||||
String tradeStatus = (String)alipayTradeQueryResponse.get("trade_status");
|
String tradeStatus = (String)alipayTradeQueryResponse.get("trade_status");
|
||||||
if(AliPayTradeStateEnum.NOTPAY.getType().equals(tradeStatus)){
|
if(AliPayTradeStateEnum.NOTPAY.getType().equals(tradeStatus)){
|
||||||
log.warn("核实订单未支付 ===> {}", orderNo);
|
log.warn("核实订单未支付 ===> {}", orderNo);
|
||||||
|
|
||||||
//如果订单未支付,则调用关单接口关闭订单
|
//如果订单未支付,则调用关单接口关闭订单
|
||||||
this.closeOrder(orderNo);
|
this.closeOrder(orderNo);
|
||||||
|
|
||||||
// 并更新商户端订单状态
|
// 并更新商户端订单状态
|
||||||
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.CLOSED);
|
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.TIMEOUT_CLOSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(AliPayTradeStateEnum.SUCCESS.getType().equals(tradeStatus)){
|
if(AliPayTradeStateEnum.SUCCESS.getType().equals(tradeStatus)){
|
||||||
log.warn("核实订单已支付 ===> {}", orderNo);
|
log.warn("核实订单已支付 ===> {}", orderNo);
|
||||||
|
|
||||||
//如果订单已支付,则更新商户端订单状态
|
//如果订单已支付,则更新商户端订单状态
|
||||||
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS);
|
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS);
|
||||||
|
|
||||||
//并记录支付日志
|
//并记录支付日志
|
||||||
paymentInfoService.createPaymentInfoForAliPay(alipayTradeQueryResponse);
|
paymentInfoService.createPaymentInfoForAliPay(alipayTradeQueryResponse);
|
||||||
}
|
}
|
||||||
@@ -252,8 +326,8 @@ public class AliPayServiceImpl implements AliPayService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (AlipayApiException e) {
|
} catch (AlipayApiException e) {
|
||||||
e.printStackTrace();
|
log.error("关单失败,原因 ===> {}",e.getMessage());
|
||||||
throw new RuntimeException("关单接口的调用失败");
|
throw new BusinessException("关单接口的调用失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,17 +342,14 @@ public class AliPayServiceImpl implements AliPayService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
log.info("调用退款API");
|
log.info("调用退款API");
|
||||||
|
|
||||||
//创建退款单
|
//创建退款单
|
||||||
RefundInfo refundInfo = refundsInfoService.createRefundByOrderNoForAliPay(orderNo, reason);
|
RefundInfo refundInfo = refundsInfoService.createRefundByOrderNoForAliPay(orderNo, reason);
|
||||||
|
|
||||||
//调用统一收单交易退款接口
|
//调用统一收单交易退款接口
|
||||||
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest ();
|
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest ();
|
||||||
|
|
||||||
//组装当前业务方法的请求参数
|
//组装当前业务方法的请求参数
|
||||||
JSONObject bizContent = new JSONObject();
|
JSONObject bizContent = new JSONObject();
|
||||||
bizContent.put("out_trade_no", orderNo);//订单编号
|
bizContent.put("out_trade_no", orderNo);//订单编号
|
||||||
BigDecimal refund = new BigDecimal(refundInfo.getRefund().toString()).divide(new BigDecimal("100"));
|
BigDecimal refund = new BigDecimal(refundInfo.getRefund().toString());
|
||||||
//BigDecimal refund = new BigDecimal("2").divide(new BigDecimal("100"));
|
//BigDecimal refund = new BigDecimal("2").divide(new BigDecimal("100"));
|
||||||
bizContent.put("refund_amount", refund);//退款金额:不能大于支付金额
|
bizContent.put("refund_amount", refund);//退款金额:不能大于支付金额
|
||||||
bizContent.put("refund_reason", reason);//退款原因(可选)
|
bizContent.put("refund_reason", reason);//退款原因(可选)
|
||||||
@@ -290,10 +361,8 @@ public class AliPayServiceImpl implements AliPayService {
|
|||||||
|
|
||||||
if(response.isSuccess()){
|
if(response.isSuccess()){
|
||||||
log.info("调用成功,返回结果 ===> " + response.getBody());
|
log.info("调用成功,返回结果 ===> " + response.getBody());
|
||||||
|
|
||||||
//更新订单状态
|
//更新订单状态
|
||||||
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_SUCCESS);
|
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_SUCCESS);
|
||||||
|
|
||||||
//更新退款单
|
//更新退款单
|
||||||
refundsInfoService.updateRefundForAliPay(
|
refundsInfoService.updateRefundForAliPay(
|
||||||
refundInfo.getRefundNo(),
|
refundInfo.getRefundNo(),
|
||||||
@@ -302,21 +371,17 @@ public class AliPayServiceImpl implements AliPayService {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
|
log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
|
||||||
|
|
||||||
//更新订单状态
|
//更新订单状态
|
||||||
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_ABNORMAL);
|
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_ABNORMAL);
|
||||||
|
|
||||||
//更新退款单
|
//更新退款单
|
||||||
refundsInfoService.updateRefundForAliPay(
|
refundsInfoService.updateRefundForAliPay(
|
||||||
refundInfo.getRefundNo(),
|
refundInfo.getRefundNo(),
|
||||||
response.getBody(),
|
response.getBody(),
|
||||||
AliPayTradeStateEnum.REFUND_ERROR.getType()); //退款失败
|
AliPayTradeStateEnum.REFUND_ERROR.getType()); //退款失败
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} catch (AlipayApiException e) {
|
} catch (AlipayApiException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException("创建退款申请失败");
|
throw new BusinessException("创建退款申请失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,7 +414,7 @@ public class AliPayServiceImpl implements AliPayService {
|
|||||||
|
|
||||||
} catch (AlipayApiException e) {
|
} catch (AlipayApiException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException("查单接口的调用失败");
|
throw new BusinessException("查单接口的调用失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,7 +428,6 @@ public class AliPayServiceImpl implements AliPayService {
|
|||||||
public String queryBill(String billDate, String type) {
|
public String queryBill(String billDate, String type) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
|
AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
|
||||||
JSONObject bizContent = new JSONObject();
|
JSONObject bizContent = new JSONObject();
|
||||||
bizContent.put("bill_type", type);
|
bizContent.put("bill_type", type);
|
||||||
@@ -383,13 +447,11 @@ public class AliPayServiceImpl implements AliPayService {
|
|||||||
return billDownloadUrl;
|
return billDownloadUrl;
|
||||||
} else {
|
} else {
|
||||||
log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
|
log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
|
||||||
throw new RuntimeException("申请账单失败");
|
throw new BusinessException("申请账单失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (AlipayApiException e) {
|
} catch (AlipayApiException e) {
|
||||||
e.printStackTrace();
|
log.error("申请账单失败,原因 ===> {}",e.getMessage());
|
||||||
throw new RuntimeException("申请账单失败");
|
throw new BusinessException("申请账单失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
159
src/main/java/com/ai/da/service/impl/CallBackServiceImpl.java
Normal file
159
src/main/java/com/ai/da/service/impl/CallBackServiceImpl.java
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
package com.ai.da.service.impl;
|
||||||
|
|
||||||
|
import com.ai.da.common.config.PayPalClient;
|
||||||
|
import com.ai.da.common.constant.PayPalCheckoutConstant;
|
||||||
|
import com.ai.da.common.utils.paypalRequest.WebhookVerifyRequest;
|
||||||
|
import com.ai.da.service.CallBackService;
|
||||||
|
import com.ai.da.service.PayPalCheckoutService;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.paypal.api.payments.Event;
|
||||||
|
import com.paypal.base.Constants;
|
||||||
|
import com.paypal.base.SDKUtil;
|
||||||
|
import com.paypal.base.rest.APIContext;
|
||||||
|
import com.paypal.base.rest.PayPalRESTException;
|
||||||
|
import com.paypal.http.HttpResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.ai.da.common.constant.PayPalCheckoutConstant.MODE;
|
||||||
|
|
||||||
|
// #Validate Webhook Sample
|
||||||
|
//
|
||||||
|
// This sample code demonstrates how to validate a webhook received on your
|
||||||
|
// web server. This sample assumes that you use the java servlet, which returns
|
||||||
|
// the HttpServletRequest object. However, you can modify this code to
|
||||||
|
// your specific case.
|
||||||
|
//
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class CallBackServiceImpl implements CallBackService {
|
||||||
|
|
||||||
|
@Value("${paypal.client-id}")
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
@Value("${paypal.client-secret}")
|
||||||
|
private String clientSecret;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PayPalClient payPalClient;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PayPalCheckoutService payPalCheckoutService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
return doPost(req, resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ##Validate Webhook
|
||||||
|
protected Boolean doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
try {
|
||||||
|
String body = getBody(req);
|
||||||
|
Map webhookEvent = new ObjectMapper().readValue(body, Map.class);
|
||||||
|
|
||||||
|
HashMap<String, Object> webhookRequest = new HashMap<>();
|
||||||
|
webhookRequest.put("auth_algo",SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-AUTH-ALGO"));
|
||||||
|
webhookRequest.put("cert_url",SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-CERT-URL"));
|
||||||
|
webhookRequest.put("transmission_id",SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-TRANSMISSION-ID"));
|
||||||
|
webhookRequest.put("transmission_sig",SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-TRANSMISSION-SIG"));
|
||||||
|
webhookRequest.put("transmission_time",SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-TRANSMISSION-TIME"));
|
||||||
|
webhookRequest.put("webhook_id",PayPalCheckoutConstant.WEBHOOK_ID);
|
||||||
|
webhookRequest.put("webhook_event",webhookEvent);
|
||||||
|
|
||||||
|
WebhookVerifyRequest webhookVerifyRequest = new WebhookVerifyRequest();
|
||||||
|
webhookVerifyRequest.authorization(payPalCheckoutService.getOAuth());
|
||||||
|
webhookVerifyRequest.requestBody(webhookRequest);
|
||||||
|
// 验签
|
||||||
|
HttpResponse<HashMap> verified = payPalClient.client(MODE, clientId, clientSecret).execute(webhookVerifyRequest);
|
||||||
|
boolean verifyResult = verified.result().get("verification_status").toString().equals("SUCCESS");
|
||||||
|
if (verifyResult){
|
||||||
|
// ### Api Context
|
||||||
|
APIContext apiContext = new APIContext(clientId, clientSecret, PayPalCheckoutConstant.MODE);
|
||||||
|
|
||||||
|
// Set the webhookId that you received when you created this webhook.
|
||||||
|
apiContext.addConfiguration(Constants.PAYPAL_WEBHOOK_ID, PayPalCheckoutConstant.WEBHOOK_ID);
|
||||||
|
Boolean result = Event.validateReceivedEvent(apiContext, getHeadersInfo(
|
||||||
|
req), body);
|
||||||
|
log.info("Webhook Validated: " + result);
|
||||||
|
|
||||||
|
if (result){
|
||||||
|
// 处理订单数据
|
||||||
|
LinkedHashMap<String,LinkedHashMap<String,String>> webhookEventMap = (LinkedHashMap<String,LinkedHashMap<String,String>>) webhookEvent;
|
||||||
|
String orderId = webhookEventMap.get("resource").get("id");
|
||||||
|
payPalCheckoutService.processOrder(orderId);
|
||||||
|
return Boolean.TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (PayPalRESTException | InvalidKeyException | NoSuchAlgorithmException | SignatureException e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
}
|
||||||
|
return Boolean.FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple helper method to help you extract the headers from HttpServletRequest object.
|
||||||
|
private static Map<String, String> getHeadersInfo(HttpServletRequest request) {
|
||||||
|
Map<String, String> map = new HashMap<String, String>();
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Enumeration headerNames = request.getHeaderNames();
|
||||||
|
while (headerNames.hasMoreElements()) {
|
||||||
|
String key = (String) headerNames.nextElement();
|
||||||
|
String value = request.getHeader(key);
|
||||||
|
map.put(key, value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple helper method to fetch request data as a string from HttpServletRequest object.
|
||||||
|
private static String getBody(HttpServletRequest request) throws IOException {
|
||||||
|
String body;
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
BufferedReader bufferedReader = null;
|
||||||
|
try {
|
||||||
|
InputStream inputStream = request.getInputStream();
|
||||||
|
if (inputStream != null) {
|
||||||
|
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
|
||||||
|
char[] charBuffer = new char[128];
|
||||||
|
int bytesRead = -1;
|
||||||
|
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
|
||||||
|
stringBuilder.append(charBuffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stringBuilder.append("");
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw ex;
|
||||||
|
} finally {
|
||||||
|
if (bufferedReader != null) {
|
||||||
|
try {
|
||||||
|
bufferedReader.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body = stringBuilder.toString();
|
||||||
|
log.info("回调参数 ===> {}", body);
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
package com.ai.da.service.impl;
|
package com.ai.da.service.impl;
|
||||||
|
|
||||||
|
|
||||||
|
import com.ai.da.common.context.UserContext;
|
||||||
import com.ai.da.common.enums.OrderStatusEnum;
|
import com.ai.da.common.enums.OrderStatusEnum;
|
||||||
import com.ai.da.common.utils.OrderNoUtils;
|
import com.ai.da.common.utils.OrderNoUtils;
|
||||||
import com.ai.da.mapper.primary.OrderInfoMapper;
|
import com.ai.da.mapper.primary.OrderInfoMapper;
|
||||||
import com.ai.da.mapper.primary.ProductMapper;
|
import com.ai.da.mapper.primary.ProductMapper;
|
||||||
import com.ai.da.mapper.primary.entity.OrderInfo;
|
import com.ai.da.mapper.primary.entity.OrderInfo;
|
||||||
import com.ai.da.mapper.primary.entity.Product;
|
import com.ai.da.mapper.primary.entity.Product;
|
||||||
|
import com.ai.da.model.vo.AuthPrincipalVo;
|
||||||
import com.ai.da.service.OrderInfoService;
|
import com.ai.da.service.OrderInfoService;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
@@ -25,9 +27,6 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
@Resource
|
@Resource
|
||||||
private ProductMapper productMapper;
|
private ProductMapper productMapper;
|
||||||
|
|
||||||
/*@Resource
|
|
||||||
private OrderInfoMapper orderInfoMapper;*/
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OrderInfo createOrderByProductId(Long productId, String paymentType) {
|
public OrderInfo createOrderByProductId(Long productId, String paymentType) {
|
||||||
|
|
||||||
@@ -39,13 +38,16 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
|
|
||||||
//获取商品信息
|
//获取商品信息
|
||||||
Product product = productMapper.selectById(productId);
|
Product product = productMapper.selectById(productId);
|
||||||
|
AuthPrincipalVo userHolder = UserContext.getUserHolder();
|
||||||
|
Long accountId = userHolder.getId();
|
||||||
|
|
||||||
//生成订单
|
//生成订单
|
||||||
orderInfo = new OrderInfo();
|
orderInfo = new OrderInfo();
|
||||||
|
orderInfo.setAccountId(accountId);
|
||||||
orderInfo.setTitle(product.getTitle());
|
orderInfo.setTitle(product.getTitle());
|
||||||
orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); //订单号
|
orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); //订单号 ??
|
||||||
orderInfo.setProductId(productId);
|
orderInfo.setProductId(productId);
|
||||||
orderInfo.setTotalFee(product.getPrice()); //分
|
orderInfo.setTotalFee(product.getPrice()); // 元
|
||||||
orderInfo.setOrderStatus(OrderStatusEnum.NOT_PAY.getType()); //未支付
|
orderInfo.setOrderStatus(OrderStatusEnum.NOT_PAY.getType()); //未支付
|
||||||
orderInfo.setPaymentType(paymentType);
|
orderInfo.setPaymentType(paymentType);
|
||||||
baseMapper.insert(orderInfo);
|
baseMapper.insert(orderInfo);
|
||||||
@@ -169,4 +171,20 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
|
OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
|
||||||
return orderInfo;
|
return orderInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<OrderInfo> getOrderByAccountId(Long accountId){
|
||||||
|
QueryWrapper<OrderInfo> qw = new QueryWrapper<>();
|
||||||
|
qw.eq("account_id",accountId);
|
||||||
|
qw.orderByDesc("create_time");
|
||||||
|
|
||||||
|
return baseMapper.selectList(qw);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateOrderNoById(Long id, String orderNo){
|
||||||
|
OrderInfo orderInfo = new OrderInfo();
|
||||||
|
orderInfo.setId(id);
|
||||||
|
orderInfo.setOrderNo(orderNo);
|
||||||
|
|
||||||
|
baseMapper.updateById(orderInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,476 @@
|
|||||||
|
package com.ai.da.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
|
import com.ai.da.common.config.PayPalClient;
|
||||||
|
import com.ai.da.common.config.exception.BusinessException;
|
||||||
|
import com.ai.da.common.constant.PayPalCheckoutConstant;
|
||||||
|
import com.ai.da.common.enums.*;
|
||||||
|
import com.ai.da.common.utils.RedisUtil;
|
||||||
|
import com.ai.da.common.utils.paypalRequest.AuthenticationRequest;
|
||||||
|
import com.ai.da.mapper.primary.entity.OrderInfo;
|
||||||
|
import com.ai.da.mapper.primary.entity.RefundInfo;
|
||||||
|
import com.ai.da.service.OrderInfoService;
|
||||||
|
import com.ai.da.service.PayPalCheckoutService;
|
||||||
|
import com.ai.da.service.PaymentInfoService;
|
||||||
|
import com.ai.da.service.RefundInfoService;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.paypal.http.HttpResponse;
|
||||||
|
import com.paypal.http.exceptions.SerializeException;
|
||||||
|
import com.paypal.http.serializer.Json;
|
||||||
|
import com.paypal.orders.*;
|
||||||
|
import com.paypal.payments.CapturesGetRequest;
|
||||||
|
import com.paypal.payments.CapturesRefundRequest;
|
||||||
|
import com.paypal.payments.RefundRequest;
|
||||||
|
import com.paypal.payments.RefundsGetRequest;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static com.ai.da.common.constant.PayPalCheckoutConstant.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
//@RefreshScope
|
||||||
|
public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
|
||||||
|
|
||||||
|
@Value("${paypal.receiver.email}")
|
||||||
|
private String receiverEmail;
|
||||||
|
|
||||||
|
@Value("${paypal.client-id}")
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
@Value("${paypal.client-secret}")
|
||||||
|
private String clientSecret;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PayPalClient payPalClient;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private OrderInfoService orderInfoService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PaymentInfoService paymentInfoService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RefundInfoService refundsInfoService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建订单的方法
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public HashMap<String, String> createOrder(Long productId,String returnUrl) throws SerializeException {
|
||||||
|
// 生成订单
|
||||||
|
log.info("生成订单");
|
||||||
|
OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, PayTypeEnum.PAYPAL.getType());
|
||||||
|
|
||||||
|
OrdersCreateRequest request = new OrdersCreateRequest();
|
||||||
|
request.header("prefer","return=representation");
|
||||||
|
request.requestBody(buildRequestBody(String.valueOf(orderInfo.getTotalFee()),returnUrl));
|
||||||
|
HttpResponse<Order> response = null;
|
||||||
|
try {
|
||||||
|
response = payPalClient.client(MODE, clientId, clientSecret).execute(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("调用paypal订单创建失败,失败原因 ===> {}", e.getMessage());
|
||||||
|
throw new BusinessException("Order creation failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
String approve = "";
|
||||||
|
assert response != null;
|
||||||
|
if (response.statusCode() == 201) {
|
||||||
|
log.info("Status Code = {}, Status = {}, OrderID = {}, Intent = {}", response.statusCode(), response.result().status(), response.result().id(), response.result().checkoutPaymentIntent());
|
||||||
|
for (LinkDescription link : response.result().links()) {
|
||||||
|
log.info("Links-{}: {} \tCall Type: {}", link.rel(), link.href(), link.method());
|
||||||
|
if(link.rel().equals("approve")) {
|
||||||
|
approve = link.href();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String totalAmount = response.result().purchaseUnits().get(0).amountWithBreakdown().currencyCode() + ":" + response.result().purchaseUnits().get(0).amountWithBreakdown().value();
|
||||||
|
log.info("Total Amount: {}", totalAmount);
|
||||||
|
String json= new JSONObject(new Json().serialize(response.result())).toString(4);
|
||||||
|
log.info("createOrder response body: {}", json);
|
||||||
|
}
|
||||||
|
|
||||||
|
String orderId = response.result().id();
|
||||||
|
orderInfoService.updateOrderNoById(orderInfo.getId(), orderId);
|
||||||
|
|
||||||
|
|
||||||
|
HashMap<String, String> returnData = new HashMap<>();
|
||||||
|
returnData.put("approve",approve);
|
||||||
|
returnData.put("orderNo",orderId);
|
||||||
|
|
||||||
|
// 需要返回地址和订单id
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成订单主体信息
|
||||||
|
*/
|
||||||
|
private OrderRequest buildRequestBody(String price, String returnUrl) {
|
||||||
|
OrderRequest orderRequest = new OrderRequest();
|
||||||
|
orderRequest.checkoutPaymentIntent(CAPTURE);
|
||||||
|
|
||||||
|
ApplicationContext applicationContext = new ApplicationContext()
|
||||||
|
.brandName(BRANDNAME)
|
||||||
|
.landingPage(LANDINGPAGE)
|
||||||
|
.cancelUrl("https://www.example.com").returnUrl(returnUrl)
|
||||||
|
.userAction(USERACTION)
|
||||||
|
.shippingPreference(SHIPPINGPREFERENCE);
|
||||||
|
orderRequest.applicationContext(applicationContext);
|
||||||
|
|
||||||
|
List<PurchaseUnitRequest> purchaseUnitRequests = new ArrayList<PurchaseUnitRequest>();
|
||||||
|
|
||||||
|
PurchaseUnitRequest purchaseUnitRequest = new PurchaseUnitRequest()
|
||||||
|
.amountWithBreakdown(new AmountWithBreakdown()
|
||||||
|
.amountBreakdown(new AmountBreakdown())
|
||||||
|
.currencyCode(CurrencyCodesEnum.HONG_KONG_DOLLAR.getCode())
|
||||||
|
.value(price));
|
||||||
|
purchaseUnitRequests.add(purchaseUnitRequest);
|
||||||
|
orderRequest.purchaseUnits(purchaseUnitRequests);
|
||||||
|
return orderRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String callback(@SuppressWarnings("rawtypes") Map map) {
|
||||||
|
|
||||||
|
log.info("paypal支付通知正在执行");
|
||||||
|
log.info("通知参数 ===> {}", map);
|
||||||
|
|
||||||
|
// log.info(map.toString());
|
||||||
|
String outTradeNo = (String)map.get("invoice");
|
||||||
|
String paymentStatus = (String)map.get("payment_status");
|
||||||
|
String amount = (String)map.get("mc_gross");
|
||||||
|
String currency = (String)map.get("mc_currency");
|
||||||
|
String paymentId = (String)map.get("txn_id");
|
||||||
|
String parentPaymentId = (String)map.get("parent_txn_id");
|
||||||
|
log.info("商家订单号 = {}", outTradeNo);
|
||||||
|
log.info("订单状态 = {}", paymentStatus);
|
||||||
|
log.info("金额 = {}", amount);
|
||||||
|
log.info("币种 = {}", currency);
|
||||||
|
log.info("流水号 = {}", paymentId);
|
||||||
|
log.info("父流水号 = {}", parentPaymentId);
|
||||||
|
|
||||||
|
if (!receiverEmail.equals(map.get("receiver_email"))) {
|
||||||
|
log.info("FAIL = 商户id错误, outTradeNo = {}", outTradeNo);
|
||||||
|
return "failure";
|
||||||
|
}
|
||||||
|
if("Completed".equals(paymentStatus)) {
|
||||||
|
//进行数据库操作
|
||||||
|
//
|
||||||
|
//
|
||||||
|
log.info("支付成功,状态为=COMPLETED");
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
|
if("Refunded".equals(paymentStatus)) {
|
||||||
|
//进行数据库操作
|
||||||
|
//
|
||||||
|
//
|
||||||
|
log.info("退款成功");
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
|
if("Pending".equals(paymentStatus) && StringUtils.isEmpty(parentPaymentId)) {
|
||||||
|
String pendingReason = String.valueOf(map.get("pending_reason"));
|
||||||
|
//进行数据库操作
|
||||||
|
//
|
||||||
|
//
|
||||||
|
log.info("订单支付成功,状态为=PENDING,产生此状态的原因是 {}", pendingReason );
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
|
if(StringUtils.isEmpty(parentPaymentId)) {
|
||||||
|
if(PayPalCheckoutConstant.PAYMENT_STATUS_REVERSED.equals(paymentStatus)
|
||||||
|
|| PayPalCheckoutConstant.PAYMENT_STATUS_CANCELED_REVERSAL.equals(paymentStatus)
|
||||||
|
|| PayPalCheckoutConstant.PAYMENT_STATUS_DENIED.equals(paymentStatus)) {
|
||||||
|
String reasonCode = String.valueOf(map.get("reason_code"));
|
||||||
|
//进行数据库操作(状态修改)
|
||||||
|
//
|
||||||
|
//
|
||||||
|
log.info("订单异常,请尽快查看处理,状态为={},产生此状态的原因是 {} ", paymentStatus, reasonCode);
|
||||||
|
return PayPalCheckoutConstant.SUCCESS;
|
||||||
|
}
|
||||||
|
if(PayPalCheckoutConstant.PAYMENT_STATUS_EXPIRED.equals(paymentStatus)
|
||||||
|
|| PayPalCheckoutConstant.PAYMENT_STATUS_CREATED.equals(paymentStatus)
|
||||||
|
|| PayPalCheckoutConstant.PAYMENT_STATUS_FAILED.equals(paymentStatus)
|
||||||
|
|| PayPalCheckoutConstant.PAYMENT_STATUS_PROCESSED.equals(paymentStatus)
|
||||||
|
|| PayPalCheckoutConstant.PAYMENT_STATUS_VOIDED.equals(paymentStatus)) {
|
||||||
|
//进行数据库操作(状态修改)
|
||||||
|
//
|
||||||
|
//
|
||||||
|
log.info("其他订单状态,订单异常,请尽快查看处理, 状态={}", paymentStatus);
|
||||||
|
return PayPalCheckoutConstant.SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "failure";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询订单信息
|
||||||
|
* @param orderNo
|
||||||
|
* @return
|
||||||
|
* @throws SerializeException
|
||||||
|
*/
|
||||||
|
public String queryOrder(String orderNo) throws SerializeException {
|
||||||
|
OrdersGetRequest request = new OrdersGetRequest(orderNo);
|
||||||
|
|
||||||
|
HttpResponse<Order> response = null;
|
||||||
|
try {
|
||||||
|
response = payPalClient.client(MODE, clientId, clientSecret).execute(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("paypal订单查询失败,失败原因 ===> {}",e.getMessage());
|
||||||
|
}
|
||||||
|
System.out.println("Status Code: " + response.statusCode());
|
||||||
|
System.out.println("Status: " + response.result().status());
|
||||||
|
System.out.println("Order id: " + response.result().id());
|
||||||
|
if(response.result().purchaseUnits().get(0).payments() != null) {
|
||||||
|
List<Capture> captures = response.result().purchaseUnits().get(0).payments().captures();
|
||||||
|
if(captures != null) {
|
||||||
|
for (Capture capture : captures) {
|
||||||
|
System.out.println("\t订单编号= " + capture.invoiceId() + "\tCapture Id= " + capture.id() + "\tCapture status= " + capture.status() + "\tCapture amount= " + capture.amount().currencyCode() + ":" + capture.amount().value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<Refund> refunds = response.result().purchaseUnits().get(0).payments().refunds();
|
||||||
|
if(refunds != null) {
|
||||||
|
for (Refund refund : refunds) {
|
||||||
|
System.out.println("\t售后编号= " + refund.invoiceId() + "\tRefund Id= " + refund.id() + "\tRefund status= " + refund.status() + "\tRefund amount= " + refund.amount().currencyCode() + ":" + refund.amount().value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("Links: ");
|
||||||
|
for (com.paypal.orders.LinkDescription link : response.result().links()) {
|
||||||
|
System.out.println("\t" + link.rel() + ": " + link.href() + "\tCall Type: " + link.method());
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Full response body:");
|
||||||
|
String json = new JSONObject(new Json().serialize(response.result())).toString(4);
|
||||||
|
System.out.println(json);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户授权支付成功,进行扣款操作
|
||||||
|
*/
|
||||||
|
public Order captureOrder(String orderId) {
|
||||||
|
OrdersCaptureRequest request = new OrdersCaptureRequest(orderId);
|
||||||
|
request.requestBody(new OrderRequest());
|
||||||
|
PayPalClient payPalClient = new PayPalClient();
|
||||||
|
HttpResponse<Order> response ;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = payPalClient.client(MODE, clientId, clientSecret).execute(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("调用paypal扣款失败,失败原因 ===> {}", e.getMessage() );
|
||||||
|
throw new BusinessException("Order deduction failed.");
|
||||||
|
}
|
||||||
|
log.info("Status Code = {}, Status = {}, OrderID = {}", response.statusCode(), response.result().status(), response.result().id());
|
||||||
|
for (LinkDescription link : response.result().links()) {
|
||||||
|
log.info("Links-{}: {} \tCall Type: {}", link.rel(), link.href(), link.method());
|
||||||
|
}
|
||||||
|
for (PurchaseUnit purchaseUnit : response.result().purchaseUnits()) {
|
||||||
|
for (Capture capture : purchaseUnit.payments().captures()) {
|
||||||
|
log.info("Capture id: {}", capture.id());
|
||||||
|
log.info("status: {}", capture.status());
|
||||||
|
log.info("invoice_id: {}", capture.invoiceId());
|
||||||
|
if("COMPLETED".equals(capture.status())) {
|
||||||
|
//进行数据库操作,修改订单状态为已支付成功,尽快发货(配合回调和CapturesGet查询确定成功)
|
||||||
|
log.info("支付成功,状态为=COMPLETED");
|
||||||
|
}
|
||||||
|
if("PENDING".equals(capture.status())) {
|
||||||
|
log.info("status_details: {}", capture.captureStatusDetails().reason());
|
||||||
|
String reason = "PENDING";
|
||||||
|
if(capture.captureStatusDetails() != null && capture.captureStatusDetails().reason() != null) {
|
||||||
|
reason = capture.captureStatusDetails().reason();
|
||||||
|
}
|
||||||
|
//进行数据库操作,修改订单状态为已支付成功,但触发了人工审核,请审核通过后再发货(配合回调和CapturesGet查询确定成功)
|
||||||
|
log.info("支付成功,状态为=PENDING : {}", reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Payer buyer = response.result().payer();
|
||||||
|
log.info("Buyer Email Address: {}", buyer.email());
|
||||||
|
log.info("Buyer Name: {} {}", buyer.name().givenName(), buyer.name().surname());
|
||||||
|
// String jsonString = JSON.toJSONString(response.result());
|
||||||
|
String json = null;
|
||||||
|
try {
|
||||||
|
json = new JSONObject(new Json().serialize(response.result())).toString(4);
|
||||||
|
} catch (SerializeException e) {
|
||||||
|
log.warn("response序列化出错,具体信息 ===> {}",e.getMessage());
|
||||||
|
}
|
||||||
|
log.info("captureOrder response body: {}", json);
|
||||||
|
// return Convert.toStr(new Json().serialize(response.result()));
|
||||||
|
return response.result();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询扣款
|
||||||
|
*/
|
||||||
|
public String queryCapture(String orderNo) throws IOException {
|
||||||
|
CapturesGetRequest request = new CapturesGetRequest("扣款id, CaptureOrder生成");
|
||||||
|
|
||||||
|
HttpResponse<com.paypal.payments.Capture> response = payPalClient.client(MODE, clientId, clientSecret).execute(request);
|
||||||
|
System.out.println("Status Code: " + response.statusCode());
|
||||||
|
System.out.println("Status: " + response.result().status());
|
||||||
|
System.out.println("Capture ids: " + response.result().id());
|
||||||
|
System.out.println("Links: ");
|
||||||
|
for (com.paypal.payments.LinkDescription link : response.result().links()) {
|
||||||
|
System.out.println("\t" + link.rel() + ": " + link.href() + "\tCall Type: " + link.method());
|
||||||
|
}
|
||||||
|
System.out.println("Full response body:");
|
||||||
|
System.out.println(new JSONObject(new Json().serialize(response.result())).toString(4));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 申请退款
|
||||||
|
*/
|
||||||
|
public Boolean refundOrder(String orderId,String reason) throws IOException {
|
||||||
|
|
||||||
|
RefundInfo refundByOrderNo = refundsInfoService.createRefundByOrderNo(orderId, reason);
|
||||||
|
|
||||||
|
OrdersGetRequest ordersGetRequest = new OrdersGetRequest(orderId);
|
||||||
|
PayPalClient payPalClient = new PayPalClient();
|
||||||
|
HttpResponse<com.paypal.orders.Order> ordersGetResponse = null;
|
||||||
|
ordersGetRequest.authorization("Bearer " + getOAuth());
|
||||||
|
boolean result ;
|
||||||
|
try {
|
||||||
|
ordersGetResponse = payPalClient.client(MODE, clientId, clientSecret).execute(ordersGetRequest);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("调用paypal订单查询失败,失败原因 ===> {}", e.getMessage());
|
||||||
|
throw new BusinessException("Order query failed");
|
||||||
|
}
|
||||||
|
String captureId = ordersGetResponse.result().purchaseUnits().get(0).payments().captures().get(0).id();
|
||||||
|
CapturesRefundRequest request = new CapturesRefundRequest(captureId);
|
||||||
|
request.authorization("Bearer " + getOAuth());
|
||||||
|
request.prefer("return=representation");
|
||||||
|
|
||||||
|
OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderId);
|
||||||
|
request.requestBody(buildRefundRequestBody(String.valueOf(orderInfo.getTotalFee()),reason));
|
||||||
|
HttpResponse<com.paypal.payments.Refund> response = null;
|
||||||
|
try {
|
||||||
|
response = payPalClient.client(MODE, clientId, clientSecret).execute(request);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("调用paypal退款申请失败,失败原因 {}", e.getMessage());
|
||||||
|
throw new BusinessException("Request for refund failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Status Code = {}, Status = {}, RefundID = {}", response.statusCode(), response.result().status(), response.result().id());
|
||||||
|
if("COMPLETED".equals(response.result().status())) {
|
||||||
|
//进行数据库操作,修改状态为已退款(配合回调和退款查询确定退款成功)
|
||||||
|
|
||||||
|
//更新订单状态
|
||||||
|
orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_SUCCESS);
|
||||||
|
|
||||||
|
refundsInfoService.updateRefundForPayPal(
|
||||||
|
refundByOrderNo.getId(),
|
||||||
|
response.result().id(),
|
||||||
|
new Gson().toJson(response.result(), com.paypal.payments.Refund.class),
|
||||||
|
AliPayTradeStateEnum.REFUND_SUCCESS.getType()); //退款成功
|
||||||
|
log.info("退款成功");
|
||||||
|
result = Boolean.TRUE;
|
||||||
|
}else {
|
||||||
|
//更新订单状态
|
||||||
|
orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_ABNORMAL);
|
||||||
|
|
||||||
|
//更新退款单
|
||||||
|
refundsInfoService.updateRefundForPayPal(
|
||||||
|
refundByOrderNo.getId(),
|
||||||
|
response.result().id(),
|
||||||
|
new Gson().toJson(response.result(), com.paypal.payments.Refund.class),
|
||||||
|
AliPayTradeStateEnum.REFUND_ERROR.getType()); //退款失败
|
||||||
|
result = Boolean.FALSE;
|
||||||
|
}
|
||||||
|
for (com.paypal.payments.LinkDescription link : response.result().links()) {
|
||||||
|
log.info("Links-{}: {} \tCall Type: {}", link.rel(), link.href(), link.method());
|
||||||
|
}
|
||||||
|
String json = new JSONObject(new Json().serialize(response.result())).toString(4);
|
||||||
|
log.info("refundOrder response body: {}", json);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RefundRequest buildRefundRequestBody(String price, String reason) {
|
||||||
|
RefundRequest refundRequest = new RefundRequest();
|
||||||
|
com.paypal.payments.Money money = new com.paypal.payments.Money();
|
||||||
|
money.currencyCode(CurrencyCodesEnum.HONG_KONG_DOLLAR.getCode());
|
||||||
|
money.value(price);
|
||||||
|
refundRequest.amount(money);
|
||||||
|
// refundRequest.invoiceId("P2020052514440001");
|
||||||
|
refundRequest.noteToPayer(reason);
|
||||||
|
try {
|
||||||
|
log.info("refund order body : {}", Convert.toStr(new Json().serialize(refundRequest)));
|
||||||
|
} catch (SerializeException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return refundRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询退款
|
||||||
|
*/
|
||||||
|
public String queryRefund(String orderNo) throws IOException {
|
||||||
|
RefundsGetRequest request = new RefundsGetRequest("退款id RefundOrder生成");
|
||||||
|
HttpResponse<com.paypal.payments.Refund> response = payPalClient.client(MODE, clientId, clientSecret).execute(request);
|
||||||
|
System.out.println("Status Code: " + response.statusCode());
|
||||||
|
System.out.println("Status: " + response.result().status());
|
||||||
|
System.out.println("Refund Id: " + response.result().id());
|
||||||
|
System.out.println("Links: ");
|
||||||
|
for (com.paypal.payments.LinkDescription link : response.result().links()) {
|
||||||
|
System.out.println("\t" + link.rel() + ": " + link.href() + "\tCall Type: " + link.method());
|
||||||
|
}
|
||||||
|
System.out.println("Full response body:");
|
||||||
|
System.out.println(new JSONObject(new Json().serialize(response.result())).toString(4));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOAuth(){
|
||||||
|
// 1、判断缓存区是否有该token
|
||||||
|
Boolean hasKey = redisUtil.hasKey(PAYPAL_TOKEN_KEY);
|
||||||
|
if (hasKey){
|
||||||
|
return redisUtil.getFromString(PAYPAL_TOKEN_KEY);
|
||||||
|
// return "A21AAKnpozur9r9omqQ2ge5aXHBBdEERi5F8FIgNYOjhhO2N7rjmkz2irh2lScpBO3s3Cqukw3eZkpYZ4YWE7rIacjv7MHmow";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2、无或者过期,重新获取token,返回
|
||||||
|
AuthenticationRequest authenticationRequest = new AuthenticationRequest();
|
||||||
|
authenticationRequest.authorization(clientId,clientSecret);
|
||||||
|
try {
|
||||||
|
HttpResponse<HashMap> authResult = payPalClient.client(MODE, clientId, clientSecret).execute(authenticationRequest);
|
||||||
|
String accessToken = authResult.result().get("access_token").toString();
|
||||||
|
long expiresIn = Long.parseLong(authResult.result().get("expires_in").toString());
|
||||||
|
// 3、存redis
|
||||||
|
redisUtil.addToString(PAYPAL_TOKEN_KEY,accessToken,expiresIn);
|
||||||
|
return accessToken;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("获取paypal token失败,失败原因 ===> {}", e.getMessage());
|
||||||
|
throw new BusinessException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理当前订单
|
||||||
|
public void processOrder(String orderId){
|
||||||
|
// 1、确定当前订单是否已经被扣款
|
||||||
|
OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderId);
|
||||||
|
if (orderInfo.getOrderStatus().equals(OrderStatusEnum.SUCCESS.getType())){
|
||||||
|
// 直接返回
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
// 发起扣款请求
|
||||||
|
Order capturedOrder = captureOrder(orderId);
|
||||||
|
// 业务处理
|
||||||
|
if (PayPalOrderStatusEnum.COMPLETED.getStatus().equals(capturedOrder.status())){
|
||||||
|
//更新订单状态
|
||||||
|
orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.SUCCESS);
|
||||||
|
//记录支付日志
|
||||||
|
paymentInfoService.createPaymentInfoForPayPal(capturedOrder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@ import com.ai.da.mapper.primary.entity.PaymentInfo;
|
|||||||
import com.ai.da.service.PaymentInfoService;
|
import com.ai.da.service.PaymentInfoService;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import com.paypal.orders.Order;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -70,8 +71,8 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
|
|||||||
String tradeStatus = params.get("trade_status");
|
String tradeStatus = params.get("trade_status");
|
||||||
//交易金额
|
//交易金额
|
||||||
String totalAmount = params.get("total_amount");
|
String totalAmount = params.get("total_amount");
|
||||||
int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
|
// int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
|
||||||
|
int totalAmountInt = new BigDecimal(totalAmount).intValue();
|
||||||
|
|
||||||
PaymentInfo paymentInfo = new PaymentInfo();
|
PaymentInfo paymentInfo = new PaymentInfo();
|
||||||
paymentInfo.setOrderNo(orderNo);
|
paymentInfo.setOrderNo(orderNo);
|
||||||
@@ -87,4 +88,27 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
|
|||||||
|
|
||||||
baseMapper.insert(paymentInfo);
|
baseMapper.insert(paymentInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createPaymentInfoForPayPal(Order order) {
|
||||||
|
|
||||||
|
log.info("记录支付日志");
|
||||||
|
|
||||||
|
// int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
|
||||||
|
int totalAmountInt = new BigDecimal(order.purchaseUnits().get(0).payments().captures().get(0).amount().value()).intValue();
|
||||||
|
|
||||||
|
PaymentInfo paymentInfo = new PaymentInfo();
|
||||||
|
paymentInfo.setOrderNo(order.id());
|
||||||
|
paymentInfo.setPaymentType(PayTypeEnum.PAYPAL.getType());
|
||||||
|
paymentInfo.setTransactionId(order.id());
|
||||||
|
paymentInfo.setTradeType("电脑网站支付");
|
||||||
|
paymentInfo.setTradeState(order.status());
|
||||||
|
paymentInfo.setPayerTotal(totalAmountInt);
|
||||||
|
|
||||||
|
Gson gson = new Gson();
|
||||||
|
String json = gson.toJson(order, Order.class);
|
||||||
|
paymentInfo.setContent(json);
|
||||||
|
|
||||||
|
baseMapper.insert(paymentInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ public class RefundInfoServiceImpl extends ServiceImpl<RefundInfoMapper, RefundI
|
|||||||
RefundInfo refundInfo = new RefundInfo();
|
RefundInfo refundInfo = new RefundInfo();
|
||||||
refundInfo.setOrderNo(orderNo);//订单编号
|
refundInfo.setOrderNo(orderNo);//订单编号
|
||||||
refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号
|
refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号
|
||||||
refundInfo.setTotalFee(orderInfo.getTotalFee());//原订单金额(分)
|
refundInfo.setTotalFee(orderInfo.getTotalFee());//原订单金额(元)
|
||||||
refundInfo.setRefund(orderInfo.getTotalFee());//退款金额(分)
|
refundInfo.setRefund(orderInfo.getTotalFee());//退款金额(元)
|
||||||
refundInfo.setReason(reason);//退款原因
|
refundInfo.setReason(reason);//退款原因
|
||||||
|
|
||||||
//保存退款订单
|
//保存退款订单
|
||||||
@@ -50,7 +50,6 @@ public class RefundInfoServiceImpl extends ServiceImpl<RefundInfoMapper, RefundI
|
|||||||
return refundInfo;
|
return refundInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 记录退款记录
|
* 记录退款记录
|
||||||
* @param content
|
* @param content
|
||||||
@@ -68,7 +67,6 @@ public class RefundInfoServiceImpl extends ServiceImpl<RefundInfoMapper, RefundI
|
|||||||
|
|
||||||
//设置要修改的字段
|
//设置要修改的字段
|
||||||
RefundInfo refundInfo = new RefundInfo();
|
RefundInfo refundInfo = new RefundInfo();
|
||||||
|
|
||||||
refundInfo.setRefundId(resultMap.get("refund_id"));//微信支付退款单号
|
refundInfo.setRefundId(resultMap.get("refund_id"));//微信支付退款单号
|
||||||
|
|
||||||
//查询退款和申请退款中的返回参数
|
//查询退款和申请退款中的返回参数
|
||||||
@@ -120,8 +118,8 @@ public class RefundInfoServiceImpl extends ServiceImpl<RefundInfoMapper, RefundI
|
|||||||
refundInfo.setOrderNo(orderNo);//订单编号
|
refundInfo.setOrderNo(orderNo);//订单编号
|
||||||
refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号
|
refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号
|
||||||
|
|
||||||
refundInfo.setTotalFee(orderInfo.getTotalFee());//原订单金额(分)
|
refundInfo.setTotalFee(orderInfo.getTotalFee());//原订单金额(元)
|
||||||
refundInfo.setRefund(orderInfo.getTotalFee());//退款金额(分)
|
refundInfo.setRefund(orderInfo.getTotalFee());//退款金额(元)
|
||||||
refundInfo.setReason(reason);//退款原因
|
refundInfo.setReason(reason);//退款原因
|
||||||
|
|
||||||
//保存退款订单
|
//保存退款订单
|
||||||
@@ -153,4 +151,23 @@ public class RefundInfoServiceImpl extends ServiceImpl<RefundInfoMapper, RefundI
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateRefundForPayPal(Long id, String refundId,String content, String refundStatus) {
|
||||||
|
|
||||||
|
//根据退款单编号修改退款单
|
||||||
|
// QueryWrapper<RefundInfo> queryWrapper = new QueryWrapper<>();
|
||||||
|
// queryWrapper.eq("order_no", orderNo);
|
||||||
|
|
||||||
|
//设置要修改的字段
|
||||||
|
RefundInfo refundInfo = new RefundInfo();
|
||||||
|
refundInfo.setId(id);
|
||||||
|
refundInfo.setRefundNo(null);
|
||||||
|
refundInfo.setRefundId(refundId);
|
||||||
|
refundInfo.setRefundStatus(refundStatus);//退款状态
|
||||||
|
refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段
|
||||||
|
|
||||||
|
//更新退款单
|
||||||
|
// baseMapper.update(refundInfo, queryWrapper);
|
||||||
|
baseMapper.updateById(refundInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ spring.security.jwtExpiration=8640000000
|
|||||||
#spring security权限设置 认证了token还要认证权限 不然报错Full authentication is required to access this resource
|
#spring security权限设置 认证了token还要认证权限 不然报错Full authentication is required to access this resource
|
||||||
spring.security.ignorePaths=/,/favicon.ico,/doc.html,/webjars/**,/swagger-resources,/v2/api-docs,\
|
spring.security.ignorePaths=/,/favicon.ico,/doc.html,/webjars/**,/swagger-resources,/v2/api-docs,\
|
||||||
/api/account/**,/api/element/**,/api/python/**,/api/design/**,/api/history/**,/api/library/**,/api/third/party/**,/api/generate/**,/api/workspace/**,/api/classification/**,\
|
/api/account/**,/api/element/**,/api/python/**,/api/design/**,/api/history/**,/api/library/**,/api/third/party/**,/api/generate/**,/api/workspace/**,/api/classification/**,\
|
||||||
/api/product/**,/api/ali-pay/**,/api/order-info/**
|
/api/product/**,/api/ali-pay/**,/api/order-info/**,/api/paypal/**
|
||||||
spring.security.authApi=/auth/login
|
spring.security.authApi=/auth/login
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
5
src/main/resources/paypal-sandbox.properties
Normal file
5
src/main/resources/paypal-sandbox.properties
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
paypal.client-id=ATbaebYi7-GXWRWJqwRLYMzKEbwjh4BFRqD4Y13i4lZq0rplWIM_IpPrtPKpdkAt_KrPXd6IJTwsDqa5
|
||||||
|
|
||||||
|
paypal.client-secret=EHWWJqGmmbfjLXqCUpGrvxRYBPPtWvA3hR5ZaAyHlGSVJiHoQPS8skbNaJ9h39VObnchUbgiY2pPu__s
|
||||||
|
|
||||||
|
paypal.receiver.email=sb-ukxfk29608925@business.example.com
|
||||||
Reference in New Issue
Block a user