重庆分公司,新征程启航

为企业提供网站建设、域名注册、服务器等服务

微信接口java代码 微信开发者接口

怎么用java调用微信支付接口

java调用微信支付接口方法:

10年积累的做网站、成都网站设计经验,可以快速应对客户对网站的新想法和需求。提供各种问题对应的解决方案。让选择我们的客户得到更好、更有力的网络服务。我虽然不认识你,你也不认识我。但先网站制作后付款的网站建设流程,更有铁岭免费网站建设让你可以放心的选择与我们合作。

RequestHandler requestHandler = new RequestHandler(super.getRequest(),super.getResponse());

//获取token //两小时内有效,两小时后重新获取

Token = requestHandler.GetToken();

//更新token 到应用中

requestHandler.getTokenReal();

System.out.println("微信支付获取token=======================:" +Token);

//requestHandler 初始化

requestHandler.init();

requestHandler.init(appid,appsecret, appkey,partnerkey, key);

// --------------------------------本地系统生成订单-------------------------------------

// 设置package订单参数

SortedMapString, String packageParams = new TreeMapString, String();

packageParams.put("bank_type", "WX"); // 支付类型

packageParams.put("body", "xxxx"); // 商品描述

packageParams.put("fee_type", "1"); // 银行币种

packageParams.put("input_charset", "UTF-8"); // 字符集

packageParams.put("notify_url", ""); // 通知地址 这里的通知地址使用外网地址测试,注意80端口是否打开。

packageParams.put("out_trade_no", no); // 商户订单号

packageParams.put("partner", partenerid); // 设置商户号

packageParams.put("spbill_create_ip", super.getRequest().getRemoteHost()); // 订单生成的机器IP,指用户浏览器端IP

packageParams.put("total_fee", String.valueOf(rstotal)); // 商品总金额,以分为单位

// 设置支付参数

SortedMapString, String signParams = new TreeMapString, String();

signParams.put("appid", appid);

signParams.put("noncestr", noncestr);

signParams.put("traceid", PropertiesUtils.getOrderNO());

signParams.put("timestamp", timestamp);

signParams.put("package", packageValue);

signParams.put("appkey", this.appkey);

// 生成支付签名,要采用URLENCODER的原始值进行SHA1算法!

String sign ="";

try {

sign = Sha1Util.createSHA1Sign(signParams);

} catch (Exception e) {

e.printStackTrace();

}

// 增加非参与签名的额外参数

signParams.put("sign_method", "sha1");

signParams.put("app_signature", sign);

// api支付拼包结束------------------------------------

//获取prepayid

String prepayid = requestHandler.sendPrepay(signParams);

System.out.println("prepayid :" + prepayid);

// --------------------------------生成完成---------------------------------------------

//生成预付快订单完成,返回给android,ios 掉起微信所需要的参数。

SortedMapString, String payParams = new TreeMapString, String();

payParams.put("appid", appid);

payParams.put("noncestr", noncestr);

payParams.put("package", "Sign=WXPay");

payParams.put("partnerid", partenerid);

payParams.put("prepayid", prepayid);

payParams.put("appkey", this.appkey);

//这里除1000 是因为参数长度限制。

int time = (int) (System.currentTimeMillis() / 1000);

payParams.put("timestamp",String.valueOf(time));

System.out.println("timestamp:" + time);

//签名

String paysign ="";

try {

paysign = Sha1Util.createSHA1Sign(payParams);

} catch (Exception e) {

e.printStackTrace();

}

payParams.put("sign", paysign);

//拼json 数据返回给客户端

BasicDBObject backObject = new BasicDBObject();

backObject.put("appid", appid);

backObject.put("noncestr", payParams.get("noncestr"));

backObject.put("package", "Sign=WXPay");

backObject.put("partnerid", payParams.get("partnerid"));

backObject.put("prepayid", payParams.get("prepayid"));

backObject.put("appkey", this.appkey);

backObject.put("timestamp",payParams.get("timestamp"));

backObject.put("sign",payParams.get("sign"));

String backstr = dataObject.toString();

System.out.println("backstr:" + backstr);

return backstr;

====================到此为止,预付款订单已生成,并且已返回客户端====================

//坐等微信服务器通知,通知的地址就是生成预付款订单的notify_url

ResponseHandler resHandler = new ResponseHandler(request, response);

resHandler.setKey(partnerkey);

//创建请求对象

//RequestHandler queryReq = new RequestHandler(request, response);

//queryReq.init();

if (resHandler.isTenpaySign() == true) {

//商户订单号

String out_trade_no = resHandler.getParameter("out_trade_no");

System.out.println("out_trade_no:" + out_trade_no);

//财付通订单号

String transaction_id = resHandler.getParameter("transaction_id");

System.out.println("transaction_id:" + transaction_id);

//金额,以分为单位

String total_fee = resHandler.getParameter("total_fee");

//如果有使用折扣券,discount有值,total_fee+discount=原请求的total_fee

String discount = resHandler.getParameter("discount");

//支付结果

String trade_state = resHandler.getParameter("trade_state");

//判断签名及结果

if ("0".equals(trade_state)) {

//------------------------------

//即时到账处理业务开始

//------------------------------

System.out.println("----------------业务逻辑执行-----------------");

//——请根据您的业务逻辑来编写程序(以上代码仅作参考)——

System.out.println("----------------业务逻辑执行完毕-----------------");

System.out.println("success"); // 请不要修改或删除

System.out.println("即时到账支付成功");

//给财付通系统发送成功信息,财付通系统收到此结果后不再进行后续通知

resHandler.sendToCFT("success");

//给微信服务器返回success 否则30分钟通知8次

return "success";

}else{

System.out.println("通知签名验证失败");

resHandler.sendToCFT("fail");

response.setCharacterEncoding("utf-8");

}

}else {

System.out.println("fail -Md5 failed");

微信开发平台中有个接口是上传多媒体文件,我用的是java 开发的,我怎么样才能在后台实现呢?代码如下:

/**

* 文件上传到微信服务器

* @param fileType 文件类型

* @param filePath 文件路径

* @return JSONObject

* @throws Exception

*/

public static JSONObject send(String fileType, String filePath) throws Exception {  

String result = null;  

File file = new File(filePath);  

if (!file.exists() || !file.isFile()) {  

throw new IOException("文件不存在");  

}  

/** 

* 第一部分 

*/  

URL urlObj = new URL(""+ getAccess_token() + "type="+fileType+"");  

HttpURLConnection con = (HttpURLConnection) urlObj.openConnection();  

con.setRequestMethod("POST"); // 以Post方式提交表单,默认get方式  

con.setDoInput(true);  

con.setDoOutput(true);  

con.setUseCaches(false); // post方式不能使用缓存  

// 设置请求头信息  

con.setRequestProperty("Connection", "Keep-Alive");  

con.setRequestProperty("Charset", "UTF-8");  

// 设置边界  

String BOUNDARY = "----------" + System.currentTimeMillis();  

con.setRequestProperty("Content-Type", "multipart/form-data; boundary="+ BOUNDARY);  

// 请求正文信息  

// 第一部分:  

StringBuilder sb = new StringBuilder();  

sb.append("--"); // 必须多两道线  

sb.append(BOUNDARY);  

sb.append("\r\n");  

sb.append("Content-Disposition: form-data;name=\"file\";filename=\""+ file.getName() + "\"\r\n");  

sb.append("Content-Type:application/octet-stream\r\n\r\n");  

byte[] head = sb.toString().getBytes("utf-8");  

// 获得输出流  

OutputStream out = new DataOutputStream(con.getOutputStream());  

// 输出表头  

out.write(head);  

// 文件正文部分  

// 把文件已流文件的方式 推入到url中  

DataInputStream in = new DataInputStream(new FileInputStream(file));  

int bytes = 0;  

byte[] bufferOut = new byte[1024];  

while ((bytes = in.read(bufferOut)) != -1) {  

out.write(bufferOut, 0, bytes);  

}  

in.close();  

// 结尾部分  

byte[] foot = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("utf-8");// 定义最后数据分隔线  

out.write(foot);  

out.flush();  

out.close();  

StringBuffer buffer = new StringBuffer();  

BufferedReader reader = null;  

try {  

// 定义BufferedReader输入流来读取URL的响应  

reader = new BufferedReader(new InputStreamReader(con.getInputStream()));  

String line = null;  

while ((line = reader.readLine()) != null) {  

//System.out.println(line);  

buffer.append(line);  

}  

if(result==null){  

result = buffer.toString();  

}  

} catch (IOException e) {  

System.out.println("发送POST请求出现异常!" + e);  

e.printStackTrace();  

throw new IOException("数据读取异常");  

} finally {  

if(reader!=null){  

reader.close();  

}  

}  

JSONObject jsonObj =new JSONObject(result);  

return jsonObj;  

}

在SAE上部署的JAVA微信应用,如何和微信接口

代码如下:

import java.io.IOException;

import java.io.PrintWriter;

import java.math.BigInteger;

import java.security.MessageDigest;

import java.security.NoSuchAlgorithmException;

import java.util.Arrays;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public class CoreServelt extends HttpServlet {

private static final long serialVersionUID = 1L;

private static final String TOKEN = "sinavoice" ;

/**

* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)

*/

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

PrintWriter out = response.getWriter() ;

String timestamp = request.getParameter("timestamp") ;

String nonce = request.getParameter("nonce") ;

String signature = request.getParameter("signature") ;

if(this.validataSignature(signature, timestamp, nonce)){

out.println(request.getParameter("echostr")) ;

}

else{

response.setStatus(HttpServletResponse.SC_HTTP_VERSION_NOT_SUPPORTED);

}

out.flush() ;

out.close() ;

}

/**

* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)

*/

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

PrintWriter out = response.getWriter() ;

// StringBuffer postText = StreamOfFileUtils.StreamForString(request.getInputStream()) ;

// log.debug(postText.toString());

out.flush();

out.close();

}

/**

* @param _signature

* @param _timestamp

* @param _nonce

* @return

*/

private boolean validataSignature(String _signature , String _timestamp , String _nonce){

String[] ttn = new String[3] ;

ttn[0]=TOKEN ;

ttn[1]=_timestamp ;

ttn[2]=_nonce ;

Arrays.sort(ttn);

String pwdSignature = String.format("%s%s%s", ttn[0],ttn[1],ttn[2]) ;

String srcSignature = _signature ;

try {

MessageDigest md = MessageDigest.getInstance("SHA1");

md.update(pwdSignature.getBytes());

pwdSignature = new BigInteger(1, md.digest()).toString(16); 

} catch (NoSuchAlgorithmException e) {

}

return srcSignature==null?false:srcSignature.equals(pwdSignature) ;

}

}

如何使用微信sdk java版

1.首先我们新建一个Java开发包WeiXinSDK

2.包路径:com.ansitech.weixin.sdk

测试的前提条件:

假如我的公众账号微信号为:vzhanqun

我的服务器地址为:

下面我们需要新建一个URL的请求地址

我们新建一个Servlet来验证URL的真实性,具体接口参考

接入指南

3.新建com.ansitech.weixin.sdk.WeixinUrlFilter.java

这里我们主要是获取微信服务器法师的验证信息,具体验证代码如下

[java] view plain copy print?

package com.ansitech.weixin.sdk;

import com.ansitech.weixin.sdk.util.SHA1;

import java.io.IOException;

import java.util.ArrayList;

import java.util.Collections;

import java.util.Comparator;

import java.util.List;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public class WeixinUrlFilter implements Filter {

//这个Token是给微信开发者接入时填的

//可以是任意英文字母或数字,长度为3-32字符

private static String Token = "vzhanqun1234567890";

@Override

public void init(FilterConfig config) throws ServletException {

System.out.println("WeixinUrlFilter启动成功!");

}

@Override

public void doFilter(ServletRequest req, ServletResponse res,

FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

HttpServletResponse response = (HttpServletResponse) res;

//微信服务器将发送GET请求到填写的URL上,这里需要判定是否为GET请求

boolean isGet = request.getMethod().toLowerCase().equals("get");

System.out.println("获得微信请求:" + request.getMethod() + " 方式");

if (isGet) {

//验证URL真实性

String signature = request.getParameter("signature");// 微信加密签名

String timestamp = request.getParameter("timestamp");// 时间戳

String nonce = request.getParameter("nonce");// 随机数

String echostr = request.getParameter("echostr");//随机字符串

ListString params = new ArrayListString();

params.add(Token);

params.add(timestamp);

params.add(nonce);

//1. 将token、timestamp、nonce三个参数进行字典序排序

Collections.sort(params, new ComparatorString() {

@Override

public int compare(String o1, String o2) {

return o1.compareTo(o2);

}

});

//2. 将三个参数字符串拼接成一个字符串进行sha1加密

String temp = SHA1.encode(params.get(0) + params.get(1) + params.get(2));

if (temp.equals(signature)) {

response.getWriter().write(echostr);

}

} else {

//处理接收消息

}

}

@Override

public void destroy() {

}

}

好了,不过这里有个SHA1算法,我这里也把SHA1算法的源码给贴出来吧!

4.新建com.ansitech.weixin.sdk.util.SHA1.java

[java] view plain copy print?

/*

* 微信公众平台(JAVA) SDK

*

* Copyright (c) 2014, Ansitech Network Technology Co.,Ltd All rights reserved.

*

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

*

*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.

*/

package com.ansitech.weixin.sdk.util;

import java.security.MessageDigest;

/**

* pTitle: SHA1算法/p

*

* @author qsyangyangqisheng274@163.com

*/

public final class SHA1 {

private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5',

'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

/**

* Takes the raw bytes from the digest and formats them correct.

*

* @param bytes the raw bytes from the digest.

* @return the formatted bytes.

*/

private static String getFormattedText(byte[] bytes) {

int len = bytes.length;

StringBuilder buf = new StringBuilder(len * 2);

// 把密文转换成十六进制的字符串形式

for (int j = 0; j len; j++) {

buf.append(HEX_DIGITS[(bytes[j] 4) 0x0f]);

buf.append(HEX_DIGITS[bytes[j] 0x0f]);

}

return buf.toString();

}

public static String encode(String str) {

if (str == null) {

return null;

}

try {

MessageDigest messageDigest = MessageDigest.getInstance("SHA1");

messageDigest.update(str.getBytes());

return getFormattedText(messageDigest.digest());

} catch (Exception e) {

throw new RuntimeException(e);

}

}

}

5.把这个Servlet配置到web.xml中

[html] view plain copy print?

filter

description微信消息接入接口/description

filter-nameWeixinUrlFilter/filter-name

filter-classcom.ansitech.weixin.sdk.WeixinUrlFilter/filter-class

/filter

filter-mapping

filter-nameWeixinUrlFilter/filter-name

url-pattern/api/vzhanqun/url-pattern

/filter-mapping

好了,接入的开发代码已经完成。

6.下面就把地址URL和密钥Token填入到微信申请成为开发者模式中吧。

如何用java开发微信

说明:

本次的教程主要是对微信公众平台开发者模式的讲解,网络上很多类似文章,但很多都让初学微信开发的人一头雾水,所以总结自己的微信开发经验,将微信开发的整个过程系统的列出,并对主要代码进行讲解分析,让初学者尽快上手。

在阅读本文之前,应对微信公众平台的官方开发文档有所了解,知道接收和发送的都是xml格式的数据。另外,在做内容回复时用到了图灵机器人的api接口,这是一个自然语言解析的开放平台,可以帮我们解决整个微信开发过程中最困难的问题,此处不多讲,下面会有其详细的调用方式。

1.1 在登录微信官方平台之后,开启开发者模式,此时需要我们填写url和token,所谓url就是我们自己服务器的接口,用WechatServlet.java来实现,相关解释已经在注释中说明,代码如下:

[java] view plain copy

package demo.servlet;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.OutputStream;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import demo.process.WechatProcess;

/**

* 微信服务端收发消息接口

*

* @author pamchen-1

*

*/

public class WechatServlet extends HttpServlet {

/**

* The doGet method of the servlet. br

*

* This method is called when a form has its tag value method equals to get.

*

* @param request

*            the request send by the client to the server

* @param response

*            the response send by the server to the client

* @throws ServletException

*             if an error occurred

* @throws IOException

*             if an error occurred

*/

public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

request.setCharacterEncoding("UTF-8");

response.setCharacterEncoding("UTF-8");

/** 读取接收到的xml消息 */

StringBuffer sb = new StringBuffer();

InputStream is = request.getInputStream();

InputStreamReader isr = new InputStreamReader(is, "UTF-8");

BufferedReader br = new BufferedReader(isr);

String s = "";

while ((s = br.readLine()) != null) {

sb.append(s);

}

String xml = sb.toString(); //次即为接收到微信端发送过来的xml数据

String result = "";

/** 判断是否是微信接入激活验证,只有首次接入验证时才会收到echostr参数,此时需要把它直接返回 */

String echostr = request.getParameter("echostr");

if (echostr != null  echostr.length()  1) {

result = echostr;

} else {

//正常的微信处理流程

result = new WechatProcess().processWechatMag(xml);

}

try {

OutputStream os = response.getOutputStream();

os.write(result.getBytes("UTF-8"));

os.flush();

os.close();

} catch (Exception e) {

e.printStackTrace();

}

}

/**

* The doPost method of the servlet. br

*

* This method is called when a form has its tag value method equals to

* post.

*

* @param request

*            the request send by the client to the server

* @param response

*            the response send by the server to the client

* @throws ServletException

*             if an error occurred

* @throws IOException

*             if an error occurred

*/

public void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

doGet(request, response);

}

}

1.2 相应的web.xml配置信息如下,在生成WechatServlet.java的同时,可自动生成web.xml中的配置。前面所提到的url处可以填写例如:http;//服务器地址/项目名/wechat.do

[html] view plain copy

?xml version="1.0" encoding="UTF-8"?

web-app version="2.5"

xmlns=""

xmlns:xsi=""

xsi:schemaLocation="

"

servlet

descriptionThis is the description of my J2EE component/description

display-nameThis is the display name of my J2EE component/display-name

servlet-nameWechatServlet/servlet-name

servlet-classdemo.servlet.WechatServlet/servlet-class

/servlet

servlet-mapping

servlet-nameWechatServlet/servlet-name

url-pattern/wechat.do/url-pattern

/servlet-mapping

welcome-file-list

welcome-fileindex.jsp/welcome-file

/welcome-file-list

/web-app

1.3 通过以上代码,我们已经实现了微信公众平台开发的框架,即开通开发者模式并成功接入、接收消息和发送消息这三个步骤。

下面就讲解其核心部分——解析接收到的xml数据,并以文本类消息为例,通过图灵机器人api接口实现智能回复。

2.1 首先看一下整体流程处理代码,包括:xml数据处理、调用图灵api、封装返回的xml数据。

[java] view plain copy

package demo.process;

import java.util.Date;

import demo.entity.ReceiveXmlEntity;

/**

* 微信xml消息处理流程逻辑类

* @author pamchen-1

*

*/

public class WechatProcess {

/**

* 解析处理xml、获取智能回复结果(通过图灵机器人api接口)

* @param xml 接收到的微信数据

* @return  最终的解析结果(xml格式数据)

*/

public String processWechatMag(String xml){

/** 解析xml数据 */

ReceiveXmlEntity xmlEntity = new ReceiveXmlProcess().getMsgEntity(xml);

/** 以文本消息为例,调用图灵机器人api接口,获取回复内容 */

String result = "";

if("text".endsWith(xmlEntity.getMsgType())){

result = new TulingApiProcess().getTulingResult(xmlEntity.getContent());

}

/** 此时,如果用户输入的是“你好”,在经过上面的过程之后,result为“你也好”类似的内容

*  因为最终回复给微信的也是xml格式的数据,所有需要将其封装为文本类型返回消息

* */

result = new FormatXmlProcess().formatXmlAnswer(xmlEntity.getFromUserName(), xmlEntity.getToUserName(), result);

return result;

}

}

2.2 解析接收到的xml数据,此处有两个类,ReceiveXmlEntity.java和ReceiveXmlProcess.java,通过反射的机制动态调用实体类中的set方法,可以避免很多重复的判断,提高代码效率,代码如下:

[java] view plain copy

package demo.entity;

/**

* 接收到的微信xml实体类

* @author pamchen-1

*

*/

public class ReceiveXmlEntity {

private String ToUserName="";

private String FromUserName="";

private String CreateTime="";

private String MsgType="";

private String MsgId="";

private String Event="";

private String EventKey="";

private String Ticket="";

private String Latitude="";

private String Longitude="";

private String Precision="";

private String PicUrl="";

private String MediaId="";

private String Title="";

private String Description="";

private String Url="";

private String Location_X="";

private String Location_Y="";

private String Scale="";

private String Label="";

private String Content="";

private String Format="";

private String Recognition="";

public String getRecognition() {

return Recognition;

}

public void setRecognition(String recognition) {

Recognition = recognition;

}

public String getFormat() {

return Format;

}

public void setFormat(String format) {

Format = format;

}

public String getContent() {

return Content;

}

public void setContent(String content) {

Content = content;

}

public String getLocation_X() {

return Location_X;

}

public void setLocation_X(String locationX) {

Location_X = locationX;

}

public String getLocation_Y() {

return Location_Y;

}

public void setLocation_Y(String locationY) {

Location_Y = locationY;

}

public String getScale() {

return Scale;

}

public void setScale(String scale) {

Scale = scale;

}

public String getLabel() {

return Label;

}

public void setLabel(String label) {

Label = label;

}

public String getTitle() {

return Title;

}

public void setTitle(String title) {

Title = title;

}

public String getDescription() {

return Description;

}

public void setDescription(String description) {

Description = description;

}

public String getUrl() {

return Url;

}

public void setUrl(String url) {

Url = url;

}

public String getPicUrl() {

return PicUrl;

}

public void setPicUrl(String picUrl) {

PicUrl = picUrl;

}

public String getMediaId() {

return MediaId;

}

public void setMediaId(String mediaId) {

MediaId = mediaId;

}

public String getEventKey() {

return EventKey;

}

public void setEventKey(String eventKey) {

EventKey = eventKey;

}

public String getTicket() {

return Ticket;

}

public void setTicket(String ticket) {

Ticket = ticket;

}

public String getLatitude() {

return Latitude;

}

public void setLatitude(String latitude) {

Latitude = latitude;

}

public String getLongitude() {

return Longitude;

}

public void setLongitude(String longitude) {

Longitude = longitude;

}

public String getPrecision() {

return Precision;

}

public void setPrecision(String precision) {

Precision = precision;

}

public String getEvent() {

return Event;

}

public void setEvent(String event) {

Event = event;

}

public String getMsgId() {

return MsgId;

}

public void setMsgId(String msgId) {

MsgId = msgId;

}

public String getToUserName() {

return ToUserName;

}

public void setToUserName(String toUserName) {


分享题目:微信接口java代码 微信开发者接口
转载来于:http://cqcxhl.com/article/doddehj.html

其他资讯

在线咨询
服务热线
服务热线:028-86922220
TOP