一.介绍
PayPay 于2018年推出,已成为日本领先的移动支付应用,拥有超过6200万的用户群。该应用程序的成功归功于其战略营销活动、用户友好的支付体验、多样化的服务以及在线和离线平台的无缝集成。自成立以来,PayPay 有效地引导了当地消费者从传统的现金交易转向数字支付的便利,使其成为日本个人日常生活中不可或缺的工具。
PayPay 与银行账户、信用卡和借记卡集成,简化了 PayPay 钱包的充值流程。无论是在线购物还是店内购物,买家都可以通过扫描二维码、条形码或输入账户信息轻松完成交易。随着全日本的广泛接受,PayPay 的支付率超过45%。
二.注册PayPay开发者账户与安装应用
开发者平台:https://developer.paypay.ne.jp/
开发环境中 PayPay 应用程序使用介绍(页面内也有相应下载方式):https://integration.paypay.ne.jp/hc/ja/articles/4414061901199-%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83%E3%81%A7PayPay%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E5%88%A9%E7%94%A8%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%81%8B
百度网盘下载:链接: https://pan.baidu.com/s/1daCBhbqGraQHNQf6pSZ10Q?pwd=frnu 提取码: frnu
账户注册成功,工作台是这样。Merchant ID、Key Id、Key Secret这三个参数用于身份验证。Test User中会有三个测试账号用于登录PayPay。
基础参数已经有了,接下来开始对接。
接口文档:https://www.paypay.ne.jp/opa/doc/v1.0/webcashier#tag/Payment/operation/getRefundDetails
三.对接网络支付
了解支付流程
1.引入依赖
<dependency>
<groupId>jp.ne.paypay</groupId>
<artifactId>paypayopa</artifactId>
<version>1.0.8</version>
</dependency>
2.身份验证
private static final String YOUR_API_KEY = "";
private static final String YOUR_API_SECRET = "";
private static final String YOUR_MERCHANT_KEY = "";
private static final ApiClient apiClient;
static {
try {
apiClient = new Configuration().getDefaultApiClient();
apiClient.setProductionMode(false);
apiClient.setApiKey(YOUR_API_KEY);
apiClient.setApiSecretKey(YOUR_API_SECRET);
apiClient.setAssumeMerchant(YOUR_MERCHANT_KEY);
} catch (ApiException e) {
throw new RuntimeException(e);
}
}
3.支付与回调
(1)创建支付二维码
/**
* 创建支付二维码
*
* @param merchantPaymentId 商户付款编号
* @param payAmount 支付金额
* @param orderDescription 支付描述
* @return 创建信息
*/
public static QRCodeResponse getQrCode(String merchantPaymentId, Integer payAmount, String orderDescription) throws ApiException {
QRCode qrCode = new QRCode();
qrCode.setAmount(new MoneyAmount().amount(payAmount).currency(MoneyAmount.CurrencyEnum.JPY));
qrCode.setMerchantPaymentId(merchantPaymentId);
qrCode.setCodeType("ORDER_QR");
qrCode.setOrderDescription(orderDescription);
qrCode.isAuthorization(false);
PaymentApi apiInstance = new PaymentApi(apiClient);
QRCodeDetails response = apiInstance.createQRCode(qrCode);
QRCodeResponse responseData = null;
if (PAY_SUCCESS.equals(response.getResultInfo().getCode())) {
responseData = response.getData();
} else {
log.info("创建二维码,Error code : {}", response.getResultInfo().getCode());
throw new RuntimeException((response.getResultInfo().getMessage()));
}
return responseData;
}
测试验证
private static final String PRODUCT_PAY_PAY = "PayOr";
@Test
public void createPaymentTest() {
String orderNo = IdUtil.getSnowflake(0, 0).nextIdStr();
System.out.println("随机生成订单号" + orderNo);
QRCodeResponse QRCodeResponse = new QRCodeResponse();
try {
String merchantPaymentId = PRODUCT_PAY_PAY + orderNo;
String orderDescription = "测试";
Integer paAmount = 1;
QRCodeResponse = getQrCode(merchantPaymentId, paAmount, orderDescription);
} catch (ApiException e) {
System.out.println("Error code : " + e.getMessage());
throw new RuntimeException(e);
}
System.out.println(QRCodeResponse);
System.out.println(QRCodeResponse.getUrl());
}
测试结果
随机生成订单号1858850799262629888
20:32:21.213 [main] INFO org.hibernate.validator.internal.util.Version -- HV000001: Hibernate Validator 8.0.1.Final
API: POST https://stg-api.sandbox.paypay.ne.jp/v2/codes
QRCodeResponse {
codeId: 04-ynVsCNYaue3wMdxP
url: https://qr-stg.sandbox.paypay.ne.jp/28180104ynVsCNYaue3wMdxP
deep Link: paypay://payment?link_key=https%3A%2F%2Fqr-stg.sandbox.paypay.ne.jp%2F28180104ynVsCNYaue3wMdxP
expiryDate: 1732019841
merchantPaymentId: PayOr1858850799262629888
amount: class MoneyAmount {
amount: 1
currency: JPY
}
orderDescription: 测试
orderItems: null
metadata: null
codeType: ORDER_QR
storeInfo: null
storeId: null
terminalId: null
requestedAt: 1732019541
redirectUrl: null
redirectType: null
isAuthorization: false
authorizationExpiry: null
}
Url :https://qr-stg.sandbox.paypay.ne.jp/28180104ynVsCNYaue3wMdxP
url及跳转连接。在浏览器中打开如下图所示。
也可复制粘贴在手机浏览器打开。
完成付款。paypay会将回调返回到服务端。
(2)支付回调
PayPay 可以发送 webhook 事件,在您的帐户发生事件时通知您的应用程序。为了能够接收通知,客户端需要设置一个 webhook url,使用 POST 方法将数据发送到客户端。所有通知数据都将有一个 notification_type 字段,客户端服务可以使用该字段来确定发生了什么事件。当您的应用程序通过 webhook 收到通知时,它应该使用 HTTP 200 OK 状态代码进行响应。虽然不是必需的,但建议使用带有简短文本消(如“OK”)的响应主体。
回调返回数据格式。
{
"notification_type": "Transaction",
"merchant_id": "",
"store_id": "",
"pos_id": "",
"order_id": "",
"merchant_order_id": "",
"authorized_at": "",
"expires_at": "",
"paid_at": null,
"order_amount": 1,
"reauth_request_id": "",
"state": "COMPLETED"
}
这里使用实体类接收回调数据。
@Data
public class PayPayNotification {
@JsonProperty("notification_type")
private String notificationType;
@JsonProperty("store_id")
private String storeId;
@JsonProperty("paid_at")
private DateTime paidAt;
@JsonProperty("expires_at")
private String expiresAt;
@JsonProperty("merchant_order_id")
private String merchantOrderId;
@JsonProperty("pos_id")
private String posId;
@JsonProperty("order_amount")
private String orderAmount;
@JsonProperty("merchant_id")
private String merchantId;
@JsonProperty("state")
private String state;
@JsonProperty("order_id")
private String orderId;
@JsonProperty("authorized_at")
private String authorizedAt;
}
在服务端,编写处理支付结果的方法。这里我只处理了 “state”: "COMPLETED"的事件。其余还有CREATED、AUTHORIZED、REAUTHORIZING、REFUNDED、FAILED、CANCELED、EXPIRED。可以根据自己需求分别处理。
特殊处理:创建支付二维码测试方法中,在随机生成的orderNo前,拼接了一个前缀 PayOr。这个特殊处理是为了满足,当需求中出现不同的支付场景时,回调需要分开处理的情况。因为PayPay不像微信支付一样可以指定回调地址。经过前缀处理之后,我们可以在回调中,根据merchantOrderId前缀的不同,分别处理。
private static final String PRODUCT_PAY_PAY = "PayOr";
@SaIgnore
@PostMapping(value = "/payNotify")
public String payNotify(@RequestBody PayPayNotification payPayNotification) {
log.info("进入支付回调---{}", payPayNotification);
String merchantOrderId = payPayNotification.getMerchantOrderId();
String prefix = merchantOrderId.substring(0, 5);
String orderNo = merchantOrderId.substring(5);
String state = payPayNotification.getState();
if (COMPLETED.getValue().equals(state) && PRODUCT_PAY_PAY.equals(prefix)) {
//因申请退款需要获得PayPay提供的付款交易ID
//所以在回调中要把 PayPay提供的付款交易ID保存
System.out.println("付款成功,paypay付款交易ID:" + payPayNotification.getOrderId());
}
return "OK";
}
服务端回调处理写好之后,要在PayPay工作台配置回调地址。PAYMENT_STATUS_WEBHOOK 为付款状态的推送。
配置完成之后,支付刚刚生成的订单,服务端会收到回调。
进入支付回调---PayPayNotification(notificationType=Transaction, storeId=null, paidAt=2024-11-19 21:34:37, expiresAt=null, merchantOrderId=PayOr1858850799262629888, posId=, orderAmount=1, merchantId=848069133234880512, state=COMPLETED, orderId=04577353467534417920, authorizedAt=null)
付款成功,paypay付款交易ID:04577353467534417920
4.申请退款
/**
* 申请退款
*
* @param merchantRefundId 商户提供的唯一退款交易ID
* @param paypayPaymentId PayPay 提供的付款交易 ID
* @param refundAmount 待退款交易金额
* @param refundReason 退款原因
* @return 退款信息
*/
public static Refund refund(String merchantRefundId, String paypayPaymentId, Integer refundAmount, String refundReason) throws ApiException {
Refund refund = new Refund();
refund.setAmount(new MoneyAmount().amount(refundAmount).currency(MoneyAmount.CurrencyEnum.JPY));
refund.setMerchantRefundId(merchantRefundId);
refund.setPaymentId(paypayPaymentId);
refund.setReason(refundReason);
refund.setRequestedAt(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC));
PaymentApi apiInstance = new PaymentApi(apiClient);
RefundDetails response = apiInstance.refundPayment(refund);
Refund responseData = new Refund();
if (PAY_SUCCESS.equals(response.getResultInfo().getCode())) {
responseData = response.getData();
} else {
log.info("申请退款,Error code : {}", response.getResultInfo().getCode());
throw new RuntimeException((response.getResultInfo().getMessage()));
}
return responseData;
}
测试验证
@Test
public void refundTest() {
String merchantRefundId = "PayOr1858850799262629888";
String paypayPaymentId = "04577353467534417920";
Integer refundAmount = 1;
String refundReason = "测试退款";
Refund refund = null;
try {
refund = refund(merchantRefundId, paypayPaymentId, refundAmount, refundReason);
} catch (ApiException e) {
throw new RuntimeException(e);
}
System.out.println("退款信息:"+refund);
}
测试结果
退款信息:class Refund {
class RefundState {
status: CREATED
acceptedAt: 1732027365
}
merchantRefundId: PayOr1858850799262629888
paymentId: 04577353467534417920
amount: class MoneyAmount {
amount: 1
currency: JPY
}
requestedAt: 1732056164
reason: 测试退款
assumeMerchant: 848069133234880512
}
5.获取退款状态
想要确认退款状态,需要调用退款详情接口。退款结果有三种:CREATED、REFUNDED、REFUND_FAILED
/**
* 获取退款状态
*
* @param merchantPaymentId 商户提供的唯一退款交易ID
* @return 退款状态
*/
public static String getRefundDetail(String merchantPaymentId) throws ApiException {
// Calling the method to get Refund Details
PaymentApi apiInstance = new PaymentApi(apiClient);
RefundDetails response = apiInstance.getRefundDetails(merchantPaymentId);
String refundStatus = "";
if (PAY_SUCCESS.equals(response.getResultInfo().getCode())) {
Refund responseData = response.getData();
refundStatus = responseData.getStatus().getValue();
} else {
log.info("退款详情,Error code : {}", response.getResultInfo().getCode());
throw new RuntimeException((response.getResultInfo().getMessage()));
}
return refundStatus;
}
测试验证
@Test
public void getRefundDetailTest() {
String merchantPaymentId = "";
String refundStatus = "";
try {
refundStatus = getRefundDetail(merchantPaymentId);
} catch (ApiException e) {
throw new RuntimeException(e);
}
System.out.println(refundStatus);
}
测试结果
API: GET https://stg-api.sandbox.paypay.ne.jp/v2/refunds/PayOr1858850799262629888
REFUNDED
如有错误,欢迎各位前来指正。